diff --git a/.gitignore b/.gitignore index 264f123e20e..331b0ec8a62 100644 --- a/.gitignore +++ b/.gitignore @@ -112,8 +112,10 @@ coverage # Expo .expo +.easignore dist/ web-build/ +.xcode.env.local # VSCode .vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4d986905d..b5e45b3c5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,57 @@ and this project adheres to [Semantic Versioning](http://semver.org/) ### Fixed +## [1.9.10] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.10) + +### Added + +- Tx Simulation (#5177) +- RPC Proxy updates (#5169) +- Remote promo sheet capabilities (#5140) + +### Changed + +- ‘An error occurred’ popup changes (#5187) + +### Fixed + +- Android navigation bar now matches app theme (#5150) +- Infinite render on swaps modal bug (#5191) + +## [1.9.9] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.9) + +### Added + +- Bug fixes +- WC improvements + +## [1.9.8] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.8) + +### Added + +- WC dapp warnings +- e2e updates +- Fee updates to NFT Mints +- Account Asset improvements + +## [1.9.7] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.7) + +### Added + +- points v0 +- prompt app reviews +- bug fixes + +## [1.9.6] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.6) + +### Added + +- in app mints +- i18n support +- Rainbow Tabs +- OP Rewards Round 2 +- more user pain points fixes + ## [1.9.5] (https://github.com/rainbow-me/rainbow/releases/tag/v1.9.5) ### Added diff --git a/README.md b/README.md index e2dc933a60e..35e2b3d7e7b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ > the Ethereum wallet that lives in your pocket! -📲️ [Available on the iOS App Store.](https://apps.apple.com/us/app/rainbow-ethereum-wallet/id1457119021) +📲️ [Available on the iOS App Store.](https://apps.apple.com/app/apple-store/id1457119021?pt=119997837&ct=github&mt=8) -🤖 [Android Beta available on Google Play Store](https://play.google.com/store/apps/details?id=me.rainbow) +🤖 [Android available on the Google Play Store](https://play.google.com/store/apps/details?id=me.rainbow&utm_campaign=gh&utm_source=referral&utm_medium=gh) + +💻 [Browser extension available on](https://rainbow.me/download?utm_campaign=gh&utm_source=referral&utm_medium=gh), [Chrome](https://chrome.google.com/webstore/detail/rainbow/opfgelmcmbiajamepnmloijbpoleiama?utm_campaign=gh&utm_source=referral&utm_medium=gh), [Brave](https://chrome.google.com/webstore/detail/rainbow/opfgelmcmbiajamepnmloijbpoleiama?utm_campaign=gh&utm_source=referral&utm_medium=gh), [Edge](https://chrome.google.com/webstore/detail/rainbow/opfgelmcmbiajamepnmloijbpoleiama?utm_campaign=gh&utm_source=referral&utm_medium=gh), [FireFox](https://addons.mozilla.org/en-US/firefox/addon/rainbow-extension/?utm_campaign=gh&utm_source=referral&utm_medium=gh), and [Arc](https://chrome.google.com/webstore/detail/rainbow/opfgelmcmbiajamepnmloijbpoleiama?utm_campaign=gh&utm_source=referral&utm_medium=gh). 🐦️ [Follow us on Twitter](https://twitter.com/rainbowdotme) diff --git a/android/app/build.gradle b/android/app/build.gradle index e1f671f1014..db0d8aee5e1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: "com.android.application" apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' +apply plugin: 'kotlin-parcelize' def getPassword(String currentUser, String keyChain) { @@ -124,13 +125,22 @@ def jscFlavor = 'org.webkit:android-jsc:+' android { def envFile = project.file('../../.env') def env =[:] - envFile.eachLine { - if (it.contains('=') && (!it.startsWith("#"))) { - def (key, value) = it.split('=') + + if (System.getenv('EAS_BUILD')) { + System.getenv().each { key, value -> env[key] = value } } + if (envFile.exists()) { + envFile.eachLine { + if (it.contains('=') && (!it.startsWith("#"))) { + def (key, value) = it.split('=') + env[key] = value + } + } + } + def props = project.file('src/main/java/me/rainbow/MainApplication.java') def contents2 = props.getText( 'UTF-8' ).replaceAll( "//_REA ", '' ).replaceAll('/\\* REA', '//_REA /* REA') props.delete() @@ -209,6 +219,10 @@ android { } } + buildFeatures { + viewBinding true + } + packagingOptions { exclude 'META-INF/DEPENDENCIES' pickFirst '**/armeabi-v7a/libc++_shared.so' @@ -218,7 +232,12 @@ android { pickFirst '**/x86/libjsc.so' pickFirst '**/armeabi-v7a/libjsc.so' } +} +configurations.all { + resolutionStrategy { + force 'com.google.android.gms:play-services-auth:17.0.0' + } } dependencies { @@ -230,7 +249,6 @@ dependencies { implementation 'com.google.android.play:core:1.8.2' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' implementation "androidx.core:core-splashscreen:1.0.0" - implementation project(':react-native-plaid-link-sdk') debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { diff --git a/android/app/src/main/java/me/rainbow/MainApplication.java b/android/app/src/main/java/me/rainbow/MainApplication.java index 360d1e51cbe..3c6b3778792 100644 --- a/android/app/src/main/java/me/rainbow/MainApplication.java +++ b/android/app/src/main/java/me/rainbow/MainApplication.java @@ -21,6 +21,7 @@ import me.rainbow.NativeModules.Internals.InternalPackage; import me.rainbow.NativeModules.RNBip39.RNBip39Package; import me.rainbow.NativeModules.RNBackHandler.RNBackHandlerPackage; +import me.rainbow.NativeModules.SystemNavigationBar.SystemNavigationBarPackage; import me.rainbow.NativeModules.RNReview.RNReviewPackage; import me.rainbow.NativeModules.RNStartTime.RNStartTimePackage; import me.rainbow.NativeModules.RNTextAnimatorPackage.RNTextAnimatorPackage; @@ -56,6 +57,7 @@ protected List getPackages() { // Packages that cannot be autolinked yet can be added manually here, for example: packages.add(new RNBip39Package()); packages.add(new RNReviewPackage()); + packages.add(new SystemNavigationBarPackage()); packages.add(new RNBackHandlerPackage()); packages.add(new RNTextAnimatorPackage()); packages.add(new RNZoomableButtonPackage()); @@ -64,8 +66,7 @@ protected List getPackages() { packages.add(new RNStartTimePackage(MainApplication.START_MARK)); packages.add(new RNHapticsPackage()); - - return packages; + return packages; } @Override diff --git a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java index 70a6275e742..f83ddc4de23 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewModule.java @@ -1,6 +1,8 @@ package me.rainbow.NativeModules.RNReview; +import android.app.Activity; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.google.android.play.core.review.ReviewInfo; @@ -9,23 +11,44 @@ import com.google.android.play.core.tasks.Task; public class RNReviewModule extends ReactContextBaseJavaModule { + private final ReactApplicationContext reactContext; + + public RNReviewModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + } + @Override public String getName() { return "RNReview"; } @ReactMethod + public void show(final Promise promise) { - ReviewManager manager = ReviewManagerFactory.create(getReactApplicationContext()); + final Activity activity = reactContext.getCurrentActivity(); + if (activity == null) { + promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist"); + return; + } + + ReviewManager manager = ReviewManagerFactory.create(reactContext); Task request = manager.requestReviewFlow(); request.addOnCompleteListener(task -> { if (task.isSuccessful()) { // We can get the ReviewInfo object ReviewInfo reviewInfo = task.getResult(); + Task flow = manager.launchReviewFlow(activity, reviewInfo); + flow.addOnCompleteListener(task1 -> { + if (task1.isSuccessful()) { + promise.resolve(null); + } else { + promise.reject("E_REVIEW_FLOW_FAILED", "Review flow failed"); + } + }); } else { - // There was some problem, continue regardless of the result. + promise.reject("E_REQUEST_REVIEW_FAILED", "Request review flow failed"); } }); } - } diff --git a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java index ff93254fab9..7317def905b 100644 --- a/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java +++ b/android/app/src/main/java/me/rainbow/NativeModules/RNReview/RNReviewPackage.java @@ -12,7 +12,7 @@ public class RNReviewPackage implements ReactPackage { @Override public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new RNReviewModule()); + return Arrays.asList(new RNReviewModule(reactContext)); } @Override diff --git a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java new file mode 100644 index 00000000000..a0c2aec95e6 --- /dev/null +++ b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarModule.java @@ -0,0 +1,176 @@ +// Created by react-native-create-bridge + +package me.rainbow.NativeModules.SystemNavigationBar; + +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.graphics.Color; +import android.os.Build; +import android.app.Activity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import androidx.annotation.UiThread; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.bridge.WritableMap; +import java.util.HashMap; +import java.util.Map; +import com.facebook.react.uimanager.IllegalViewOperationException; +import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread; + +public class SystemNavigationBarModule extends ReactContextBaseJavaModule { + public static final String REACT_CLASS = "NavigationBar"; + private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY"; + private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity"; + private static final String ERROR_API_LEVEL = "API_LEVEl"; + private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported"; + private static ReactApplicationContext reactContext = null; + private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + public SystemNavigationBarModule(ReactApplicationContext context) { + // Pass in the context to the constructor and save it so you can emit events + // https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module + super(context); + reactContext = context; + } + + public void setNavigationBarTheme(Activity activity, Boolean light) { + if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Window window = activity.getWindow(); + int flags = window.getDecorView().getSystemUiVisibility(); + if (light) { + flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } else { + flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + window.getDecorView().setSystemUiVisibility(flags); + } + } + + + @Override + public String getName() { + // Tell React the name of the module + // https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module + return REACT_CLASS; + } + + @Override + public Map getConstants() { + // Export any constants to be used in your native module + // https://facebook.github.io/react-native/docs/native-modules-android.html#the-toast-module + final Map constants = new HashMap<>(); + constants.put("EXAMPLE_CONSTANT", "example"); + + return constants; + } + + @ReactMethod + public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) { + final WritableMap map = Arguments.createMap(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (getCurrentActivity() != null) { + try { + final Window window = getCurrentActivity().getWindow(); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (color.equals("transparent") || color.equals("translucent")) { + window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + if (color.equals("transparent")) { + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + } else { + window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + setNavigationBarTheme(getCurrentActivity(), light); + map.putBoolean("success", true); + promise.resolve(map); + return; + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + if (animated) { + Integer colorFrom = window.getNavigationBarColor(); + Integer colorTo = Color.parseColor(String.valueOf(color)); + //window.setNavigationBarColor(colorTo); + ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); + colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + window.setNavigationBarColor((Integer) animator.getAnimatedValue()); + } + }); + colorAnimation.start(); + } else { + window.setNavigationBarColor(Color.parseColor(String.valueOf(color))); + } + setNavigationBarTheme(getCurrentActivity(), light); + WritableMap map = Arguments.createMap(); + map.putBoolean("success", true); + promise.resolve(map); + } + }); + } catch (IllegalViewOperationException e) { + map.putBoolean("success", false); + promise.reject("error", e); + } + + } else { + promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE)); + + } + } else { + promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE)); + } + } + + @ReactMethod + public void hideNavigationBar(Promise promise) { + try { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (getCurrentActivity() != null) { + View decorView = getCurrentActivity().getWindow().getDecorView(); + decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR); + } + } + }); + } catch (IllegalViewOperationException e) { + WritableMap map = Arguments.createMap(); + map.putBoolean("success", false); + promise.reject("error", e); + } + } + + @ReactMethod + public void showNavigationBar(Promise promise) { + try { + runOnUiThread(new Runnable() { + @Override + public void run() { + if (getCurrentActivity() != null) { + View decorView = getCurrentActivity().getWindow().getDecorView(); + int uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; + decorView.setSystemUiVisibility(uiOptions); + } + } + }); + } catch (IllegalViewOperationException e) { + WritableMap map = Arguments.createMap(); + map.putBoolean("success", false); + promise.reject("error", e); + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarPackage.java b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarPackage.java new file mode 100644 index 00000000000..f7b56ced301 --- /dev/null +++ b/android/app/src/main/java/me/rainbow/NativeModules/SystemNavigationBar/SystemNavigationBarPackage.java @@ -0,0 +1,28 @@ +package me.rainbow.NativeModules.SystemNavigationBar; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SystemNavigationBarPackage implements ReactPackage { + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new SystemNavigationBarModule(reactContext)); + return modules; + } + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index e51f7463603..a147a9685ef 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -36,47 +36,46 @@ buildscript { // in the individual module build.gradle files } allprojects { - repositories { - google() - mavenLocal() - mavenCentral { - // We don't want to fetch react-native from Maven Central as there are - // older versions over there. - content { - excludeGroup "com.facebook.react" + repositories { + google() + mavenLocal() + mavenCentral { + // We don't want to fetch react-native from Maven Central as there are + // older versions over there. + content { + excludeGroup "com.facebook.react" + } } + jcenter() + maven { + url 'https://maven.google.com' + } + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") + } + maven { + // All of Detox' artifacts are provided via the npm module + url "$rootDir/../node_modules/detox/Detox-android" + } + maven { url 'https://www.jitpack.io' } } - jcenter() - maven { - url 'https://maven.google.com' - } - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url("$rootDir/../node_modules/react-native/android") - } - maven { - // Android JSC is installed from npm - url("$rootDir/../node_modules/jsc-android/dist") - } - maven { - // All of Detox' artifacts are provided via the npm module - url "$rootDir/../node_modules/detox/Detox-android" - } - maven { url 'https://www.jitpack.io' } - } - afterEvaluate { project -> - if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) { - android { - defaultConfig { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion - minSdkVersion rootProject.ext.minSdkVersion - + afterEvaluate { project -> + if (!project.name.equalsIgnoreCase("app") && project.hasProperty("android")) { + android { + defaultConfig { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + minSdkVersion rootProject.ext.minSdkVersion + + } } } } } } -} - diff --git a/android/settings.gradle b/android/settings.gradle index 3a308d759a1..5f460b3484a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -16,8 +16,5 @@ project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules */ includeBuild('../node_modules/@react-native/gradle-plugin') -include ':react-native-plaid-link-sdk' -project(':react-native-plaid-link-sdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-plaid-link-sdk/android') - apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") useExpoModules() \ No newline at end of file diff --git a/e2e/deeplinks.spec.js b/e2e/deeplinks.spec.js index f4b93783926..3dae8513620 100644 --- a/e2e/deeplinks.spec.js +++ b/e2e/deeplinks.spec.js @@ -109,19 +109,19 @@ describe('Deeplinks spec', () => { await Helpers.swipe('profile-sheet', 'down'); }); - it.skip('should be able to handle ethereum payments urls for ETH (mainnet)', async () => { + it('should be able to handle ethereum payments urls for ETH (mainnet)', async () => { const url = escapeUrl('ethereum:payment-brunobarbieri.eth@1?value=1e2'); await testEthereumDeeplink(url, false); }); - it.skip('should be able to handle ethereum payments urls for ETH (optimism)', async () => { + it('should be able to handle ethereum payments urls for ETH (optimism)', async () => { const url = escapeUrl('ethereum:payment-brunobarbieri.eth@10?value=1e15'); await testEthereumDeeplink(url, false); }); // FIXME: when doing open deeplinks with cold start, the account assets state // comes back empty, find a fix and then change these tests to cold-start again - it.skip('should be able to handle ethereum payments urls for DAI (mainnet)', async () => { + it('should be able to handle ethereum payments urls for DAI (mainnet)', async () => { const url = escapeUrl( 'ethereum:0x6b175474e89094c44da98b954eedeac495271d0f@1/transfer?address=brunobarbieri.eth&uint256=1e18' ); @@ -130,14 +130,14 @@ describe('Deeplinks spec', () => { // FIXME: when doing open deeplinks with cold start, the account assets state // comes back empty, find a fix and then change these tests to cold-start again - it.skip('should be able to handle ethereum payments urls for ETH (arbitrum)', async () => { + it('should be able to handle ethereum payments urls for ETH (arbitrum)', async () => { const url = 'ethereum:payment-brunobarbieri.eth@42161?value=1e15'; await testEthereumDeeplink(url, false); }); // FIXME: when doing open deeplinks with cold start, the account assets state // comes back empty, find a fix and then change these tests to cold-start again - it.skip('should be able to handle ethereum payments urls for DAI (optimism)', async () => { + it('should be able to handle ethereum payments urls for DAI (optimism)', async () => { const url = escapeUrl( 'ethereum:0xda10009cbd5d07dd0cecc66161fc93d7c9000da1@10/transfer?address=brunobarbieri.eth&uint256=1e15' ); @@ -146,7 +146,7 @@ describe('Deeplinks spec', () => { // FIXME: when doing open deeplinks with cold start, the account assets state // comes back empty, find a fix and then change these tests to cold-start again - it.skip('should be able to handle ethereum payments urls for MATIC (polygon)', async () => { + it('should be able to handle ethereum payments urls for MATIC (polygon)', async () => { const url = escapeUrl('ethereum:payment-brunobarbieri.eth@137?value=1e15'); await testEthereumDeeplink(url, false); }); diff --git a/e2e/discoverSheetFlow.spec.js b/e2e/discoverSheetFlow.spec.js index 517162d91ec..801c20d63bb 100644 --- a/e2e/discoverSheetFlow.spec.js +++ b/e2e/discoverSheetFlow.spec.js @@ -45,14 +45,24 @@ describe('Discover Screen Flow', () => { await Helpers.enableSynchronization(); }); - it('Should navigate to the Profile screen after swiping right', async () => { - await Helpers.swipe('wallet-screen', 'right', 'slow'); + it('Should navigate to Discover screen after swiping left', async () => { + await Helpers.swipe('wallet-screen', 'left', 'slow'); + await Helpers.checkIfVisible('discover-header'); + }); + + it('Should navigate to the Profile screen after swiping left', async () => { + await Helpers.waitAndTap('tab-bar-icon-ProfileScreen'); await Helpers.checkIfVisible('profile-screen'); }); - it('Should navigate to Discover screen after swiping left twice', async () => { + it('Should navigate to the Points screen after swiping left', async () => { await Helpers.swipe('profile-screen', 'left', 'slow'); - await Helpers.swipe('wallet-screen', 'left', 'slow'); + await Helpers.checkIfVisible('points-screen'); + }); + + it('Should navigate back to Discover screen after swiping right twice', async () => { + await Helpers.swipe('points-screen', 'right', 'slow'); + await Helpers.swipe('profile-screen', 'right', 'slow'); await Helpers.checkIfVisible('discover-header'); }); diff --git a/e2e/sendSheetFlow.spec.js b/e2e/sendSheetFlow.spec.js index f70dc4a652b..a0164e416e2 100644 --- a/e2e/sendSheetFlow.spec.js +++ b/e2e/sendSheetFlow.spec.js @@ -100,19 +100,18 @@ describe('Send Sheet Interaction Flow', () => { // populate // // SKIPPING all tests after this one, as the app is in the wrong state - it.skip('Should show show Contact Button & Asset List on valid public address', async () => { + it('Should show show Contact Button & Asset List on valid public address', async () => { await Helpers.clearField('send-asset-form-field'); await Helpers.checkIfVisible('send-asset-form-field'); - await Helpers.typeText( + await Helpers.replaceTextInField( 'send-asset-form-field', - '0xF0f21ab2012731542731df194cfF6c77d29cB31A', - false + '0xF0f21ab2012731542731df194cfF6c77d29cB31A' ); // await Helpers.checkIfVisible('add-contact-button'); await Helpers.checkIfVisible('send-asset-list', 20000); }); - it.skip('Should show show Contact Button & Asset List on valid ENS & Unstoppable addresses', async () => { + it('Should show show Contact Button & Asset List on valid ENS & Unstoppable addresses', async () => { await Helpers.clearField('send-asset-form-field'); await Helpers.checkIfVisible('send-asset-form-field'); await Helpers.typeText( @@ -193,32 +192,32 @@ describe('Send Sheet Interaction Flow', () => { await Helpers.waitAndTap('send-asset-form-ETH-token'); }); - it.skip('Should display Asset Form after tapping on asset ETH', async () => { + it('Should display Asset Form after tapping on asset ETH', async () => { await Helpers.checkIfVisible('send-asset-ETH-token'); await Helpers.waitAndTap('send-asset-ETH-token'); await Helpers.checkIfVisible('selected-asset-field-input'); }); - it.skip('Should display max button on asset input focus ETH', async () => { + it('Should display max button on asset input focus ETH', async () => { await Helpers.checkIfVisible('selected-asset-field-input'); await Helpers.waitAndTap('selected-asset-field-input'); await Helpers.checkIfElementByTextIsVisible('Max'); }); - it.skip('Should display max button on asset quantity input focus ETH', async () => { + it('Should display max button on asset quantity input focus ETH', async () => { await Helpers.checkIfVisible('selected-asset-quantity-field-input'); await Helpers.waitAndTap('selected-asset-quantity-field-input'); await Helpers.checkIfElementByTextIsVisible('Max'); }); - it.skip('Should display Insufficient Funds button if exceeds asset balance ETH', async () => { + it('Should display Insufficient Funds button if exceeds asset balance ETH', async () => { await Helpers.checkIfVisible('selected-asset-field-input'); await Helpers.waitAndTap('selected-asset-field-input'); await Helpers.typeText('selected-asset-field-input', '9999', true); await Helpers.checkIfElementByTextIsVisible('Insufficient Funds'); }); - it.skip('Should prepend a 0 to quantity field on input of . ETH', async () => { + it('Should prepend a 0 to quantity field on input of . ETH', async () => { await Helpers.waitAndTap('send-asset-form-ETH-token'); await Helpers.waitAndTap('send-asset-ETH-token'); await Helpers.checkIfVisible('selected-asset-quantity-field-input'); @@ -227,7 +226,7 @@ describe('Send Sheet Interaction Flow', () => { await Helpers.checkIfElementByTextIsVisible('0.'); }); - it.skip('Should only show a max of 2 decimals in quantity field ETH', async () => { + it('Should only show a max of 2 decimals in quantity field ETH', async () => { await Helpers.waitAndTap('send-asset-form-ETH-token'); await Helpers.waitAndTap('send-asset-ETH-token'); await Helpers.checkIfVisible('selected-asset-quantity-field-input'); diff --git a/e2e/walletAvatarOptions.disabled.js b/e2e/walletAvatarOptions.disabled.js index 88a17edd51b..b4eb380de3f 100644 --- a/e2e/walletAvatarOptions.disabled.js +++ b/e2e/walletAvatarOptions.disabled.js @@ -22,7 +22,8 @@ describe('Wallet avatar options', () => { await Helpers.waitAndTap('import-sheet-button'); await Helpers.waitAndTap('wallet-info-submit-button'); await Helpers.checkIfVisible('wallet-screen', 40000); - await Helpers.swipe('wallet-screen', 'right', 'slow'); + await Helpers.swipe('wallet-screen', 'left', 'slow'); + await Helpers.swipe('discover-home', 'left', 'slow'); await Helpers.checkIfVisible('profile-screen', 40000); }); @@ -55,7 +56,8 @@ describe('Wallet avatar options', () => { } // Remove this once https://github.com/rainbow-me/rainbow/pull/4115 is merged. await Helpers.relaunchApp(); - await Helpers.swipe('wallet-screen', 'right', 'slow'); + await Helpers.swipe('wallet-screen', 'left', 'slow'); + await Helpers.swipe('discover-home', 'left', 'slow'); await Helpers.checkIfVisible('profile-screen', 40000); // TODO: check that wallet has different address (otherwise it means that creating wallet failed!). diff --git a/eas.json b/eas.json index 490b85a8e49..a1fc03fbd9e 100644 --- a/eas.json +++ b/eas.json @@ -10,7 +10,8 @@ }, "ios": { "buildConfiguration": "Debug", - "simulator": true + "simulator": true, + "scheme": "Rainbow" } }, "preview": { diff --git a/globals.d.ts b/globals.d.ts index f6628687064..ba2a17c2f89 100644 --- a/globals.d.ts +++ b/globals.d.ts @@ -88,6 +88,11 @@ declare module 'react-native-dotenv' { export const LOG_DEBUG: string; export const QUIET_OLD_LOGGER: string; export const ARC_GRAPHQL_API_KEY: string; + export const METADATA_GRAPHQL_API_KEY: string; export const RESERVOIR_API_KEY_PROD: string; export const RESERVOIR_API_KEY_DEV: string; + export const RPC_PROXY_BASE_URL_PROD: string; + export const RPC_PROXY_BASE_URL_DEV: string; + export const RPC_PROXY_API_KEY_PROD: string; + export const RPC_PROXY_API_KEY_DEV: string; } diff --git a/ios/.xcode.env.local b/ios/.xcode.env.local deleted file mode 100644 index a645db8eb28..00000000000 --- a/ios/.xcode.env.local +++ /dev/null @@ -1 +0,0 @@ -export NODE_BINARY="/Users/derek/.nvm/versions/node/v16.18.0/bin/node" diff --git a/ios/Images.xcassets/badges/arbitrum.imageset/Contents.json b/ios/Images.xcassets/badges/arbitrum.imageset/Contents.json new file mode 100644 index 00000000000..d8baae252df --- /dev/null +++ b/ios/Images.xcassets/badges/arbitrum.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "arbitrum.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "arbitrum@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "arbitrum@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum.png b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum.png new file mode 100644 index 00000000000..ad5c0420ad0 Binary files /dev/null and b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum.png differ diff --git a/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@2x.png b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@2x.png new file mode 100644 index 00000000000..697bc2f6ec4 Binary files /dev/null and b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@2x.png differ diff --git a/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@3x.png b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@3x.png new file mode 100644 index 00000000000..4153e6706f2 Binary files /dev/null and b/ios/Images.xcassets/badges/arbitrum.imageset/arbitrum@3x.png differ diff --git a/ios/Images.xcassets/badges/base.imageset/Contents.json b/ios/Images.xcassets/badges/base.imageset/Contents.json new file mode 100644 index 00000000000..eb69ae688b8 --- /dev/null +++ b/ios/Images.xcassets/badges/base.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "base.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "base@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "base@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/base.imageset/base.png b/ios/Images.xcassets/badges/base.imageset/base.png new file mode 100644 index 00000000000..8d365d203f2 Binary files /dev/null and b/ios/Images.xcassets/badges/base.imageset/base.png differ diff --git a/ios/Images.xcassets/badges/base.imageset/base@2x.png b/ios/Images.xcassets/badges/base.imageset/base@2x.png new file mode 100644 index 00000000000..1aded5c29f9 Binary files /dev/null and b/ios/Images.xcassets/badges/base.imageset/base@2x.png differ diff --git a/ios/Images.xcassets/badges/base.imageset/base@3x.png b/ios/Images.xcassets/badges/base.imageset/base@3x.png new file mode 100644 index 00000000000..570dc2e8618 Binary files /dev/null and b/ios/Images.xcassets/badges/base.imageset/base@3x.png differ diff --git a/ios/Images.xcassets/badges/bsc.imageset/Contents.json b/ios/Images.xcassets/badges/bsc.imageset/Contents.json new file mode 100644 index 00000000000..d32402f583b --- /dev/null +++ b/ios/Images.xcassets/badges/bsc.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "bsc.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bsc@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bsc@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/bsc.imageset/bsc.png b/ios/Images.xcassets/badges/bsc.imageset/bsc.png new file mode 100644 index 00000000000..009bae7a844 Binary files /dev/null and b/ios/Images.xcassets/badges/bsc.imageset/bsc.png differ diff --git a/ios/Images.xcassets/badges/bsc.imageset/bsc@2x.png b/ios/Images.xcassets/badges/bsc.imageset/bsc@2x.png new file mode 100644 index 00000000000..f164fd04e32 Binary files /dev/null and b/ios/Images.xcassets/badges/bsc.imageset/bsc@2x.png differ diff --git a/ios/Images.xcassets/badges/bsc.imageset/bsc@3x.png b/ios/Images.xcassets/badges/bsc.imageset/bsc@3x.png new file mode 100644 index 00000000000..0253b66c648 Binary files /dev/null and b/ios/Images.xcassets/badges/bsc.imageset/bsc@3x.png differ diff --git a/ios/Images.xcassets/badges/ethereum.imageset/Contents.json b/ios/Images.xcassets/badges/ethereum.imageset/Contents.json new file mode 100644 index 00000000000..862983cf63e --- /dev/null +++ b/ios/Images.xcassets/badges/ethereum.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "ethereum.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "ethereum@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ethereum@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/ethereum.imageset/ethereum.png b/ios/Images.xcassets/badges/ethereum.imageset/ethereum.png new file mode 100644 index 00000000000..ce612bb3da8 Binary files /dev/null and b/ios/Images.xcassets/badges/ethereum.imageset/ethereum.png differ diff --git a/ios/Images.xcassets/badges/ethereum.imageset/ethereum@2x.png b/ios/Images.xcassets/badges/ethereum.imageset/ethereum@2x.png new file mode 100644 index 00000000000..c3aa91dd13f Binary files /dev/null and b/ios/Images.xcassets/badges/ethereum.imageset/ethereum@2x.png differ diff --git a/ios/Images.xcassets/badges/ethereum.imageset/ethereum@3x.png b/ios/Images.xcassets/badges/ethereum.imageset/ethereum@3x.png new file mode 100644 index 00000000000..237f8a091cd Binary files /dev/null and b/ios/Images.xcassets/badges/ethereum.imageset/ethereum@3x.png differ diff --git a/ios/Images.xcassets/badges/optimism.imageset/Contents.json b/ios/Images.xcassets/badges/optimism.imageset/Contents.json new file mode 100644 index 00000000000..8e6b667039b --- /dev/null +++ b/ios/Images.xcassets/badges/optimism.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "optimism.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "optimism@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "optimism@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/optimism.imageset/optimism.png b/ios/Images.xcassets/badges/optimism.imageset/optimism.png new file mode 100644 index 00000000000..36d6cf2e73f Binary files /dev/null and b/ios/Images.xcassets/badges/optimism.imageset/optimism.png differ diff --git a/ios/Images.xcassets/badges/optimism.imageset/optimism@2x.png b/ios/Images.xcassets/badges/optimism.imageset/optimism@2x.png new file mode 100644 index 00000000000..81f4ced3fb9 Binary files /dev/null and b/ios/Images.xcassets/badges/optimism.imageset/optimism@2x.png differ diff --git a/ios/Images.xcassets/badges/optimism.imageset/optimism@3x.png b/ios/Images.xcassets/badges/optimism.imageset/optimism@3x.png new file mode 100644 index 00000000000..d5dab748c01 Binary files /dev/null and b/ios/Images.xcassets/badges/optimism.imageset/optimism@3x.png differ diff --git a/ios/Images.xcassets/badges/polygon.imageset/Contents.json b/ios/Images.xcassets/badges/polygon.imageset/Contents.json new file mode 100644 index 00000000000..8a53e7b74b0 --- /dev/null +++ b/ios/Images.xcassets/badges/polygon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "polygon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "polygon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "polygon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/polygon.imageset/polygon.png b/ios/Images.xcassets/badges/polygon.imageset/polygon.png new file mode 100644 index 00000000000..e0a9fa08e9c Binary files /dev/null and b/ios/Images.xcassets/badges/polygon.imageset/polygon.png differ diff --git a/ios/Images.xcassets/badges/polygon.imageset/polygon@2x.png b/ios/Images.xcassets/badges/polygon.imageset/polygon@2x.png new file mode 100644 index 00000000000..9ff81cf978b Binary files /dev/null and b/ios/Images.xcassets/badges/polygon.imageset/polygon@2x.png differ diff --git a/ios/Images.xcassets/badges/polygon.imageset/polygon@3x.png b/ios/Images.xcassets/badges/polygon.imageset/polygon@3x.png new file mode 100644 index 00000000000..9e3bc2e7d04 Binary files /dev/null and b/ios/Images.xcassets/badges/polygon.imageset/polygon@3x.png differ diff --git a/ios/Images.xcassets/badges/xdai.imageset/Contents.json b/ios/Images.xcassets/badges/xdai.imageset/Contents.json new file mode 100644 index 00000000000..f0ee7cf8808 --- /dev/null +++ b/ios/Images.xcassets/badges/xdai.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "xdai.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "xdai@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "xdai@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/xdai.imageset/xdai.png b/ios/Images.xcassets/badges/xdai.imageset/xdai.png new file mode 100644 index 00000000000..f62aac06a14 Binary files /dev/null and b/ios/Images.xcassets/badges/xdai.imageset/xdai.png differ diff --git a/ios/Images.xcassets/badges/xdai.imageset/xdai@2x.png b/ios/Images.xcassets/badges/xdai.imageset/xdai@2x.png new file mode 100644 index 00000000000..4ea6a80914c Binary files /dev/null and b/ios/Images.xcassets/badges/xdai.imageset/xdai@2x.png differ diff --git a/ios/Images.xcassets/badges/xdai.imageset/xdai@3x.png b/ios/Images.xcassets/badges/xdai.imageset/xdai@3x.png new file mode 100644 index 00000000000..2969bc76ba9 Binary files /dev/null and b/ios/Images.xcassets/badges/xdai.imageset/xdai@3x.png differ diff --git a/ios/Images.xcassets/badges/zora.imageset/Contents.json b/ios/Images.xcassets/badges/zora.imageset/Contents.json new file mode 100644 index 00000000000..0847fbff926 --- /dev/null +++ b/ios/Images.xcassets/badges/zora.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "zora.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "zora@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "zora@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Images.xcassets/badges/zora.imageset/zora.png b/ios/Images.xcassets/badges/zora.imageset/zora.png new file mode 100644 index 00000000000..2310c3dca62 Binary files /dev/null and b/ios/Images.xcassets/badges/zora.imageset/zora.png differ diff --git a/ios/Images.xcassets/badges/zora.imageset/zora@2x.png b/ios/Images.xcassets/badges/zora.imageset/zora@2x.png new file mode 100644 index 00000000000..5256774e273 Binary files /dev/null and b/ios/Images.xcassets/badges/zora.imageset/zora@2x.png differ diff --git a/ios/Images.xcassets/badges/zora.imageset/zora@3x.png b/ios/Images.xcassets/badges/zora.imageset/zora@3x.png new file mode 100644 index 00000000000..3504d713a84 Binary files /dev/null and b/ios/Images.xcassets/badges/zora.imageset/zora@3x.png differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f4de8736bda..0342a4a2454 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -25,7 +25,7 @@ PODS: - ExpoModulesCore - EXFont (11.4.0): - ExpoModulesCore - - Expo (49.0.13): + - Expo (49.0.16): - ExpoModulesCore - ExpoKeepAwake (12.3.0): - ExpoModulesCore @@ -610,8 +610,8 @@ PODS: - React-Core - react-native-cloud-fs (2.6.1): - React - - react-native-get-random-values (1.5.0): - - React + - react-native-get-random-values (1.9.0): + - React-Core - react-native-ios-context-menu (1.15.3): - React-Core - react-native-mail (4.1.0): @@ -654,9 +654,9 @@ PODS: - react-native-text-input-mask (2.0.0): - React - RNInputMask - - react-native-udp (2.7.0): + - react-native-udp (4.1.7): - CocoaAsyncSocket - - React + - React-Core - react-native-version-number (0.3.6): - React - react-native-video (5.2.1): @@ -799,7 +799,7 @@ PODS: - React - RNFastImage (8.5.11): - React-Core - - SDWebImage (~> 5.12.5) + - SDWebImage (~> 5.11.1) - SDWebImageWebPCoder (~> 0.8.4) - RNFBApp (18.3.0): - Firebase/CoreOnly (= 10.12.0) @@ -849,7 +849,7 @@ PODS: - React - RNPermissions (3.6.1): - React-Core - - RNReactNativeHapticFeedback (1.11.0): + - RNReactNativeHapticFeedback (2.2.0): - React-Core - RNReanimated (3.4.2): - DoubleConversion @@ -897,9 +897,9 @@ PODS: - React-Core - RNTextSize (4.0.0-rc.1): - React - - SDWebImage (5.12.6): - - SDWebImage/Core (= 5.12.6) - - SDWebImage/Core (5.12.6) + - SDWebImage (5.11.1): + - SDWebImage/Core (= 5.11.1) + - SDWebImage/Core (5.11.1) - SDWebImageWebPCoder (0.8.5): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.10) @@ -917,7 +917,7 @@ PODS: - React - SSZipArchive (2.2.3) - swift-vibrant (1.0.0) - - TcpSockets (3.3.2): + - TcpSockets (4.0.0): - CocoaAsyncSocket - React - TOCropViewController (2.6.1) @@ -1410,7 +1410,7 @@ SPEC CHECKSUMS: EXConstants: ce5bbea779da8031ac818c36bea41b10e14d04e1 EXFileSystem: 2b826a3bf1071a4b80a8457e97124783d1ac860e EXFont: 738c44c390953ebcbab075a4848bfbef025fd9ee - Expo: e7d2116b947e2e6fdeb09ee4f2754f819426d1b6 + Expo: fcfd60c1ed6806dee5103b210335ae0c72f675ed ExpoKeepAwake: be4cbd52d9b177cde0fd66daa1913afa3161fc1d ExpoModulesCore: 51cb2e7ab4c8da14be3f40b66d54c1781002e99d FBLazyVector: 4cce221dd782d3ff7c4172167bba09d58af67ccb @@ -1482,7 +1482,7 @@ SPEC CHECKSUMS: react-native-cameraroll: aff50ec1df9d054626dceca9336e6644e153d32f react-native-change-icon: ea9bb7255b09e89f41cbf282df16eade91ab1833 react-native-cloud-fs: e7103d1f693c57b481f820fa5e6b6b0522a5a31e - react-native-get-random-values: 1404bd5cc0ab0e287f75ee1c489555688fc65f89 + react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb react-native-ios-context-menu: e529171ba760a1af7f2ef0729f5a7f4d226171c5 react-native-mail: a864fb211feaa5845c6c478a3266de725afdce89 react-native-menu: 5b9b82dabcc779cebde15d292ccbcba4469ff311 @@ -1499,7 +1499,7 @@ SPEC CHECKSUMS: react-native-skia: d0b0aab6bb1f146eb6f379fb671b719deabd20fb react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865 react-native-text-input-mask: 07227297075f9653315f43b0424d596423a01736 - react-native-udp: 96a517e5a121cfe69f4b05eeeafefe00c623debf + react-native-udp: df79c3cb72c4e71240cd3ce4687bfb8a137140d5 react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f react-native-video: c26780b224543c62d5e1b2a7244a5cd1b50e8253 react-native-video-cache: 0f8b1b2195f234eabd507cfebdd91c2e82695008 @@ -1530,7 +1530,7 @@ SPEC CHECKSUMS: RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6 RNDeviceInfo: 6f20764111df002b4484f90cbe0a861be29bcc6c RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3 - RNFastImage: 945abf54742505d790d9024d230c69b1e866bc88 + RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7 RNFBApp: 147c3274daa84d7410d457510548d5fb3b317e37 RNFBCrashlytics: 4adc1698782408ecc0b0cf9e9b9060c82726afab RNFBMessaging: 0e98a1452a60e6828fcd7139b1ad45ebf4452aa8 @@ -1546,7 +1546,7 @@ SPEC CHECKSUMS: RNNotifee: 8ee6ddabfc44b16600b978983162e0d4db20ce19 RNOS: 31db6fa4a197d179afbba9e6b4d28d450a7f250b RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c - RNReactNativeHapticFeedback: 653a8c126a0f5e88ce15ffe280b3ff37e1fbb285 + RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 RNReanimated: 726395a2fa2f04cea340274ba57a4e659bc0d9c1 RNScreens: 6a8a3c6b808aa48dca1780df7b73ea524f602c63 RNSentry: fbbdcd7213058e3de5fbaa452b25a06a16b4b382 @@ -1554,7 +1554,7 @@ SPEC CHECKSUMS: RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 RNTextSize: 21c94a67819fbf2c358a219bf6d251e3be9e5f29 - SDWebImage: a47aea9e3d8816015db4e523daff50cfd294499d + SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d segment-analytics-react-native: 2b90f606cda9234dd110d117c4ffc918d9ba29bb Sentry: 0c5cd63d714187b4a39c331c1f0eb04ba7868341 @@ -1564,7 +1564,7 @@ SPEC CHECKSUMS: SRSRadialGradient: e5a34825dff88c9cf773b58bf39ed6a02e59a084 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 swift-vibrant: 3def73c5c281db74f420ec386590d9c1c5b0995c - TcpSockets: bd31674146c0931a064fc254a59812dfd1a73ae0 + TcpSockets: a8eb6b5867fe643e6cfed5db2e4de62f4d1e8fd0 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 ToolTipMenu: 8ac61aded0fbc4acfe7e84a7d0c9479d15a9a382 Yoga: 8796b55dba14d7004f980b54bcc9833ee45b28ce diff --git a/ios/Rainbow.xcodeproj/project.pbxproj b/ios/Rainbow.xcodeproj/project.pbxproj index 9eae39ec16c..866dad3cf87 100644 --- a/ios/Rainbow.xcodeproj/project.pbxproj +++ b/ios/Rainbow.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -41,7 +41,6 @@ A4277DA323CFE85F0042BAF4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4277DA223CFE85F0042BAF4 /* Theme.swift */; }; A4D04BA923D12F99008C1DEC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BA823D12F99008C1DEC /* Button.swift */; }; A4D04BAC23D12FD5008C1DEC /* ButtonManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */; }; - A9F312A50F28216E0C2FC393 /* libPods-PriceWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 73790DA1833435B55F6617C8 /* libPods-PriceWidgetExtension.a */; }; AA6228EF24272F510078BDAA /* SF-Pro-Rounded-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = AA6228EB24272B200078BDAA /* SF-Pro-Rounded-Bold.otf */; }; AA6228F024272F510078BDAA /* SF-Pro-Rounded-Heavy.otf in Resources */ = {isa = PBXBuildFile; fileRef = AA6228EC24272B200078BDAA /* SF-Pro-Rounded-Heavy.otf */; }; AA6228F124272F510078BDAA /* SF-Pro-Rounded-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */; }; @@ -237,7 +236,6 @@ 6635730524939991006ACFA6 /* SafeStoreReview.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SafeStoreReview.m; sourceTree = ""; }; 664612EC2748489B00B43F5A /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; 664612ED274848B000B43F5A /* SelectTokenIntent.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SelectTokenIntent.entitlements; sourceTree = ""; }; - 664ECDBBD5A459CD3744964A /* Pods-ImageNotification.localrelease.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImageNotification.localrelease.xcconfig"; path = "Target Support Files/Pods-ImageNotification/Pods-ImageNotification.localrelease.xcconfig"; sourceTree = ""; }; 6655FFB325BB2B0700642961 /* ThemeModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThemeModule.m; sourceTree = ""; }; 668ADB2C25A4E3A40050859D /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = ""; }; 668ADB2E25A4E3A40050859D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -260,14 +258,11 @@ A4277DA223CFE85F0042BAF4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; A4D04BA823D12F99008C1DEC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; A4D04BAB23D12FD5008C1DEC /* ButtonManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ButtonManager.m; sourceTree = ""; }; - A56EC7992F140DBE3A0BC44D /* Pods-PriceWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.debug.xcconfig"; sourceTree = ""; }; - A873FCD38CDC7FEB5E93833F /* Pods-Rainbow.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rainbow.debug.xcconfig"; path = "Target Support Files/Pods-Rainbow/Pods-Rainbow.debug.xcconfig"; sourceTree = ""; }; AA6228EA24272B200078BDAA /* SF-Pro-Rounded-Semibold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Semibold.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Semibold.otf"; sourceTree = ""; }; AA6228EB24272B200078BDAA /* SF-Pro-Rounded-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Bold.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Bold.otf"; sourceTree = ""; }; AA6228EC24272B200078BDAA /* SF-Pro-Rounded-Heavy.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Heavy.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Heavy.otf"; sourceTree = ""; }; AA6228ED24272B200078BDAA /* SF-Pro-Rounded-Medium.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Medium.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Medium.otf"; sourceTree = ""; }; AA6228EE24272B200078BDAA /* SF-Pro-Rounded-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "SF-Pro-Rounded-Regular.otf"; path = "../src/assets/fonts/SF-Pro-Rounded-Regular.otf"; sourceTree = ""; }; - AD490CBDAC6554605BF1847A /* Pods-PriceWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PriceWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-PriceWidgetExtension/Pods-PriceWidgetExtension.release.xcconfig"; sourceTree = ""; }; B0C692B061D7430D8194DC98 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; B50C9AE92A9D18DC00EB0019 /* adworld@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@3x.png"; sourceTree = ""; }; B50C9AEA2A9D18DC00EB0019 /* adworld@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "adworld@2x.png"; sourceTree = ""; }; @@ -758,7 +753,6 @@ 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */, 668ADB3225A4E3A40050859D /* Embed App Extensions */, 50BEB9FB3B730622F0A5DD5D /* [CP] Embed Pods Frameworks */, F035C5C329B8110C85DC7544 /* [CP-User] [RNFB] Core Configuration */, @@ -1068,31 +1062,6 @@ shellPath = /bin/sh; shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\nif [[ ${PODS_ROOT} ]]; then\n echo \"info: Exec FirebaseCrashlytics Run from Pods\"\n \"${PODS_ROOT}/FirebaseCrashlytics/run\"\nelse\n echo \"info: Exec FirebaseCrashlytics Run from framework\"\n \"${PROJECT_DIR}/FirebaseCrashlytics.framework/run\"\nfi\n"; }; - 9FF961FEA7AF435FA18ED988 /* Upload Debug Symbols to Sentry */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Rainbow/Pods-Rainbow-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/Plaid/LinkKit.framework/LinkKit", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "Upload Debug Symbols to Sentry"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LinkKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym\n"; - }; A58B0C5FE766C521E6C62DB4 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1353,7 +1322,11 @@ INFOPLIST_KEY_CFBundleDisplayName = ImageNotification; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Rainbow. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -1403,7 +1376,11 @@ INFOPLIST_KEY_CFBundleDisplayName = ImageNotification; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Rainbow. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; @@ -1452,7 +1429,11 @@ INFOPLIST_KEY_CFBundleDisplayName = ImageNotification; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Rainbow. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; @@ -1501,7 +1482,11 @@ INFOPLIST_KEY_CFBundleDisplayName = ImageNotification; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2022 Rainbow. All rights reserved."; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; @@ -1566,14 +1551,17 @@ INFOPLIST_FILE = Rainbow/Info.plist; INTENTS_CODEGEN_LANGUAGE = "Objective-C"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", "$(inherited)", "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.11; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1630,14 +1618,17 @@ INFOPLIST_FILE = Rainbow/Info.plist; INTENTS_CODEGEN_LANGUAGE = "Objective-C"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", "$(inherited)", "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.11; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1739,14 +1730,17 @@ INFOPLIST_FILE = Rainbow/Info.plist; INTENTS_CODEGEN_LANGUAGE = "Objective-C"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", "$(inherited)", "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.11; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -1849,14 +1843,17 @@ INFOPLIST_FILE = Rainbow/Info.plist; INTENTS_CODEGEN_LANGUAGE = "Objective-C"; IPHONEOS_DEPLOYMENT_TARGET = 13.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(SDKROOT)/usr/lib/swift", "$(inherited)", "$(PROJECT_DIR)", ); LLVM_LTO = YES; - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.11; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ( @@ -2008,7 +2005,11 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PriceWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2058,14 +2059,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PriceWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2106,14 +2112,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PriceWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.PriceWidget"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2154,14 +2165,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = PriceWidget/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.PriceWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.PriceWidget"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2200,7 +2216,11 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SelectTokenIntent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; @@ -2248,14 +2268,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SelectTokenIntent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2294,14 +2319,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SelectTokenIntent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -2340,14 +2370,19 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = SelectTokenIntent/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.5; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; PRODUCT_BUNDLE_IDENTIFIER = me.rainbow.SelectTokenIntent; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.rainbow.SelectTokenIntent"; SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/package.json b/package.json index d01f1eb3a3a..f2217906b6d 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "private": true, "scripts": { "eas-build-pre-install": "chmod -R +x ./scripts && ./scripts/eas-build-pre-install.sh", + "eas-build:dev:ios": "chmod -R +x ./scripts && ./scripts/setup-env.sh && eas build --platform ios --profile development", + "eas-build:dev:android": "chmod -R +x ./scripts && ./scripts/setup-env.sh && eas build --platform android --profile development", + "eas-build:dev:local:ios": "chmod -R +x ./scripts && ./scripts/setup-env.sh && eas build --platform ios --profile development --local", + "eas-build:dev:local:android": "chmod -R +x ./scripts && ./scripts/setup-env.sh && eas build --platform android --profile development --local", "setup": "yarn install && yarn graphql-codegen:install && yarn ds:install && yarn allow-scripts && yarn postinstall && yarn graphql-codegen", "setup-ci": "./scripts/setup-ci.sh", "adb-all": "adb devices | tail -n +2 | cut -sf 1 | xargs -I {} adb -s {}", @@ -99,7 +103,6 @@ "@notifee/react-native": "5.6.0", "@rainbow-me/react-native-animated-number": "0.0.2", "@rainbow-me/swaps": "0.6.0", - "@ratio.me/ratio-react-native-library": "0.8.5", "@react-native-async-storage/async-storage": "1.18.2", "@react-native-camera-roll/camera-roll": "5.7.1", "@react-native-community/blur": "4.3.2", @@ -191,6 +194,7 @@ "make-color-more-chill": "0.2.2", "match-sorter": "6.3.0", "mnemonist": "0.38.1", + "moti": "0.27.2", "multiformats": "9.6.2", "nanoid": "3.2.0", "p-queue": "7.2.0", @@ -227,10 +231,10 @@ "react-native-dotenv": "2.4.2", "react-native-exit-app": "1.1.0", "react-native-extra-dimensions-android": "1.2.2", - "react-native-fast-image": "8.6.3", + "react-native-fast-image": "8.5.11", "react-native-fs": "2.16.6", "react-native-gesture-handler": "1.10.3", - "react-native-get-random-values": "1.5.0", + "react-native-get-random-values": "1.9.0", "react-native-haptic-feedback": "2.2.0", "react-native-image-crop-picker": "0.37.3", "react-native-indicators": "0.17.0", @@ -248,7 +252,6 @@ "react-native-pager-view": "5.4.24", "react-native-palette-full": "1.2.0", "react-native-permissions": "3.6.1", - "react-native-plaid-link-sdk": "9.1.0", "react-native-quick-md5": "3.0.3", "react-native-radial-gradient": "rainbow-me/react-native-radial-gradient#b99ab59d27dba70364ef516bd5193c37657ba95c", "react-native-randombytes": "3.5.3", @@ -266,12 +269,13 @@ "react-native-store-review": "0.1.5", "react-native-svg": "13.9.0", "react-native-tab-view": "3.5.1", - "react-native-tcp": "3.3.2", + "react-native-tcp": "4.0.0", "react-native-text-input-mask": "2.0.0", "react-native-text-size": "rainbow-me/react-native-text-size#15b21c9f88c6df0d1b5e0f2ba792fe59b5dc255a", "react-native-tooltip": "rainbow-me/react-native-tooltip#e0e88d212b5b7f350e5eabba87f588a32e0f2590", "react-native-tooltips": "rainbow-me/react-native-tooltips#d278f506782d3f39d68ae830c92b8afe670f0e3b", - "react-native-udp": "2.7.0", + "react-native-udp": "4.1.7", + "react-native-url-polyfill": "2.0.0", "react-native-version-number": "0.3.6", "react-native-video": "5.2.1", "react-native-video-cache": "2.0.5", diff --git a/patches/react-native+0.72.0.patch b/patches/react-native+0.72.0.patch index 95c9d705ce1..84789ee633a 100644 --- a/patches/react-native+0.72.0.patch +++ b/patches/react-native+0.72.0.patch @@ -10,6 +10,22 @@ index 26961d7..ee5943d 100644 } else if (!global.nativeExtensions) { const bridgeConfig = global.__fbBatchedBridgeConfig; invariant( +diff --git a/node_modules/react-native/React/Views/RCTView.m b/node_modules/react-native/React/Views/RCTView.m +index 619509f..a9477e5 100644 +--- a/node_modules/react-native/React/Views/RCTView.m ++++ b/node_modules/react-native/React/Views/RCTView.m +@@ -831,6 +831,11 @@ static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) + layer.backgroundColor = backgroundColor; + layer.contents = nil; + layer.needsDisplayOnBoundsChange = NO; ++ if (@available(iOS 13.0, *)) { ++ if (layer.cornerRadius < MIN(self.bounds.size.height, self.bounds.size.width) / 2) { ++ layer.cornerCurve = kCACornerCurveContinuous; ++ } ++ } + layer.mask = nil; + return; + } diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h b/node_modules/react-native/ReactCommon/react/renderer/components/rncore/ComponentDescriptors.h new file mode 100644 index 0000000..37b7d8d diff --git a/patches/react-native-fast-image+8.5.11+fix-make-image.patch b/patches/react-native-fast-image+8.5.11+fix-make-image.patch new file mode 100644 index 00000000000..05d872989f9 --- /dev/null +++ b/patches/react-native-fast-image+8.5.11+fix-make-image.patch @@ -0,0 +1,23 @@ +diff --git a/node_modules/react-native-fast-image/ios/FastImage/FFFastImageView.m b/node_modules/react-native-fast-image/ios/FastImage/FFFastImageView.m +index 9c0f1d3..db4da88 100644 +--- a/node_modules/react-native-fast-image/ios/FastImage/FFFastImageView.m ++++ b/node_modules/react-native-fast-image/ios/FastImage/FFFastImageView.m +@@ -70,12 +70,12 @@ - (void)setImageColor:(UIColor *)imageColor { + } + + - (UIImage*)makeImage:(UIImage *)image withTint:(UIColor *)color { +- UIImage *newImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; +- UIGraphicsBeginImageContextWithOptions(image.size, NO, newImage.scale); +- [color set]; +- [newImage drawInRect:CGRectMake(0, 0, image.size.width, newImage.size.height)]; +- newImage = UIGraphicsGetImageFromCurrentImageContext(); +- UIGraphicsEndImageContext(); ++ UIImage* newImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; ++ UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:image.size]; ++ newImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { ++ [color setFill]; ++ [newImage drawInRect:CGRectMake(0, 0, image.size.width, newImage.size.height)]; ++ }]; + return newImage; + } + diff --git a/patches/react-native-fast-image+8.5.11.patch b/patches/react-native-fast-image+8.5.11+initial.patch similarity index 95% rename from patches/react-native-fast-image+8.5.11.patch rename to patches/react-native-fast-image+8.5.11+initial.patch index 4cdfa8a6157..2e6cdea1aae 100644 --- a/patches/react-native-fast-image+8.5.11.patch +++ b/patches/react-native-fast-image+8.5.11+initial.patch @@ -1,15 +1,3 @@ -diff --git a/node_modules/react-native-fast-image/RNFastImage.podspec b/node_modules/react-native-fast-image/RNFastImage.podspec -index db0fada..54d8d5b 100644 ---- a/node_modules/react-native-fast-image/RNFastImage.podspec -+++ b/node_modules/react-native-fast-image/RNFastImage.podspec -@@ -16,6 +16,6 @@ Pod::Spec.new do |s| - s.source_files = "ios/**/*.{h,m}" - - s.dependency 'React-Core' -- s.dependency 'SDWebImage', '~> 5.11.1' -+ s.dependency 'SDWebImage', '~> 5.12.5' - s.dependency 'SDWebImageWebPCoder', '~> 0.8.4' - end diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java index 361417b..f167e91 100644 --- a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java diff --git a/patches/react-native-haptic-feedback+1.11.0.patch b/patches/react-native-haptic-feedback+1.11.0.patch deleted file mode 100644 index 4ce4a082bde..00000000000 --- a/patches/react-native-haptic-feedback+1.11.0.patch +++ /dev/null @@ -1,42 +0,0 @@ -diff --git a/node_modules/react-native-haptic-feedback/index.js b/node_modules/react-native-haptic-feedback/index.js -index 083c4fc..132ed79 100644 ---- a/node_modules/react-native-haptic-feedback/index.js -+++ b/node_modules/react-native-haptic-feedback/index.js -@@ -1,4 +1,4 @@ --import { NativeModules } from 'react-native'; -+import { NativeModules, Platform } from 'react-native'; - - const defaultOptions = { - enableVibrateFallback: false, -@@ -10,7 +10,30 @@ class RNReactNativeHapticFeedback { - const triggerOptions = createTriggerOptions(options) - - try { -- NativeModules.RNReactNativeHapticFeedback.trigger(type, triggerOptions); -+ if (Platform.OS === "android") { -+ switch (type) { -+ case 'impactLight': -+ NativeModules.RNRainbowHaptics.impact("light") -+ break; -+ case 'impactHeavy': -+ NativeModules.RNRainbowHaptics.impact("heavy") -+ break; -+ case 'notificationSuccess': -+ NativeModules.RNRainbowHaptics.notification("success") -+ break; -+ case 'notificationWarning': -+ NativeModules.RNRainbowHaptics.notification("warning") -+ break; -+ case 'notificationError': -+ NativeModules.RNRainbowHaptics.notification("error") -+ break; -+ default: -+ NativeModules.RNRainbowHaptics.selection() -+ break; -+ } -+ } else { -+ NativeModules.RNReactNativeHapticFeedback.trigger(type, triggerOptions); -+ } - } catch (err) { - console.warn('RNReactNativeHapticFeedback is not available'); - } diff --git a/patches/react-native-haptic-feedback+2.2.0.patch b/patches/react-native-haptic-feedback+2.2.0.patch new file mode 100644 index 00000000000..ec8f1eecf01 --- /dev/null +++ b/patches/react-native-haptic-feedback+2.2.0.patch @@ -0,0 +1,42 @@ +diff --git a/node_modules/react-native-haptic-feedback/build/index.js b/node_modules/react-native-haptic-feedback/build/index.js +index 0961b2d..d9124b4 100644 +--- a/node_modules/react-native-haptic-feedback/build/index.js ++++ b/node_modules/react-native-haptic-feedback/build/index.js +@@ -1,4 +1,4 @@ +-import { NativeModules } from "react-native"; ++import { NativeModules, Platform } from "react-native"; + import { HapticFeedbackTypes } from "./types"; + export * from "./types"; + const defaultOptions = { +@@ -13,7 +13,30 @@ class RNReactNativeHapticFeedback { + const hapticFeedback = isTurboModuleEnabled + ? require("./NativeHapticFeedback").default + : NativeModules.RNHapticFeedback; +- hapticFeedback.trigger(type, triggerOptions); ++ if (Platform.OS === "android") { ++ switch (type) { ++ case 'impactLight': ++ NativeModules.RNRainbowHaptics.impact("light") ++ break; ++ case 'impactHeavy': ++ NativeModules.RNRainbowHaptics.impact("heavy") ++ break; ++ case 'notificationSuccess': ++ NativeModules.RNRainbowHaptics.notification("success") ++ break; ++ case 'notificationWarning': ++ NativeModules.RNRainbowHaptics.notification("warning") ++ break; ++ case 'notificationError': ++ NativeModules.RNRainbowHaptics.notification("error") ++ break; ++ default: ++ NativeModules.RNRainbowHaptics.selection() ++ break; ++ } ++ } else { ++ NativeModules.RNReactNativeHapticFeedback.trigger(type, triggerOptions); ++ } + } + catch (err) { + console.warn("RNReactNativeHapticFeedback is not available"); diff --git a/patches/react-native-pager-view+5.4.24.patch b/patches/react-native-pager-view+5.4.24.patch index d2489e125e2..749591965af 100644 --- a/patches/react-native-pager-view+5.4.24.patch +++ b/patches/react-native-pager-view+5.4.24.patch @@ -10,3 +10,16 @@ index 81070b2..2f1d2b9 100644 vp.adapter = ViewPagerAdapter() //https://github.com/callstack/react-native-viewpager/issues/183 vp.isSaveEnabled = false +diff --git a/node_modules/react-native-pager-view/ios/ReactNativePageView.m b/node_modules/react-native-pager-view/ios/ReactNativePageView.m +index 9f8ed5b..9a1f8c9 100644 +--- a/node_modules/react-native-pager-view/ios/ReactNativePageView.m ++++ b/node_modules/react-native-pager-view/ios/ReactNativePageView.m +@@ -107,7 +107,7 @@ + if([subview isKindOfClass:UIScrollView.class]){ + ((UIScrollView *)subview).delegate = self; + ((UIScrollView *)subview).keyboardDismissMode = _dismissKeyboard; +- ((UIScrollView *)subview).delaysContentTouches = YES; ++ ((UIScrollView *)subview).delaysContentTouches = NO; + self.scrollView = (UIScrollView *)subview; + } + } diff --git a/patches/react-native-plaid-link-sdk+9.1.0.patch b/patches/react-native-plaid-link-sdk+9.1.0.patch deleted file mode 100644 index 21ace6d8391..00000000000 --- a/patches/react-native-plaid-link-sdk+9.1.0.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/react-native-plaid-link-sdk/android/build.gradle b/node_modules/react-native-plaid-link-sdk/android/build.gradle -index c1c0b7d..4e720d1 100644 ---- a/node_modules/react-native-plaid-link-sdk/android/build.gradle -+++ b/node_modules/react-native-plaid-link-sdk/android/build.gradle -@@ -12,7 +12,7 @@ allprojects { - - - buildscript { -- ext.kotlin_version = '1.3.61' -+ ext.kotlin_version = '1.6.20' - repositories { - google() - mavenCentral() diff --git a/patches/react-native-tcp+3.3.2.patch b/patches/react-native-tcp+4.0.0.patch similarity index 99% rename from patches/react-native-tcp+3.3.2.patch rename to patches/react-native-tcp+4.0.0.patch index 5f0c59dc958..fc53feae2d7 100644 --- a/patches/react-native-tcp+3.3.2.patch +++ b/patches/react-native-tcp+4.0.0.patch @@ -1,12 +1,12 @@ diff --git a/node_modules/react-native-tcp/TcpSockets.podspec b/node_modules/react-native-tcp/TcpSockets.podspec -index 43ce35d..c1dd344 100644 +index 43ce35d..1db1821 100644 --- a/node_modules/react-native-tcp/TcpSockets.podspec +++ b/node_modules/react-native-tcp/TcpSockets.podspec @@ -19,5 +19,6 @@ Pod::Spec.new do |s| s.preserve_paths = 'README.md', 'package.json', '**/*.js' s.source_files = 'ios/**/*.{h,m}' s.dependency 'React' -+ s.dependency "CocoaAsyncSocket" ++ s.dependency 'CocoaAsyncSocket' end diff --git a/node_modules/react-native-tcp/android/build.gradle b/node_modules/react-native-tcp/android/build.gradle @@ -22,31 +22,6 @@ index c582eb7..5785289 100644 + implementation 'com.facebook.react:react-native:+' + implementation 'com.koushikdutta.async:androidasync:2.1.6' } -diff --git a/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSocketManager.java b/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSocketManager.java -index df2007f..8353cd1 100644 ---- a/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSocketManager.java -+++ b/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSocketManager.java -@@ -1,6 +1,6 @@ - package com.peel.react; - --import android.support.annotation.Nullable; -+import androidx.annotation.Nullable; - import android.util.SparseArray; - - import com.koushikdutta.async.AsyncNetworkSocket; -diff --git a/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSockets.java b/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSockets.java -index 1bc07d2..8e6d5d5 100644 ---- a/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSockets.java -+++ b/node_modules/react-native-tcp/android/src/main/java/com/peel/react/TcpSockets.java -@@ -5,7 +5,7 @@ - - package com.peel.react; - --import android.support.annotation.Nullable; -+import androidx.annotation.Nullable; - import android.util.Base64; - - import com.facebook.common.logging.FLog; diff --git a/node_modules/react-native-tcp/ios/CocoaAsyncSocket/GCDAsyncSocket.h b/node_modules/react-native-tcp/ios/CocoaAsyncSocket/GCDAsyncSocket.h deleted file mode 100644 index 828951f..0000000 @@ -9647,19 +9622,6 @@ index 6b1de9d..4f74b78 100644 extern NSString *const RCTTCPErrorDomain; -diff --git a/node_modules/react-native-tcp/ios/TcpSockets.h b/node_modules/react-native-tcp/ios/TcpSockets.h -index 47ba018..e7781b4 100644 ---- a/node_modules/react-native-tcp/ios/TcpSockets.h -+++ b/node_modules/react-native-tcp/ios/TcpSockets.h -@@ -6,7 +6,7 @@ - #import "TcpSocketClient.h" - - #import --#import "CocoaAsyncSocket/GCDAsyncSocket.h" -+#import "GCDAsyncSocket.h" - - @interface TcpSockets : RCTEventEmitter - diff --git a/node_modules/react-native-tcp/ios/TcpSockets.xcodeproj/project.pbxproj b/node_modules/react-native-tcp/ios/TcpSockets.xcodeproj/project.pbxproj index 92a2b8b..5f3b485 100644 --- a/node_modules/react-native-tcp/ios/TcpSockets.xcodeproj/project.pbxproj diff --git a/patches/react-native-udp+2.7.0.patch b/patches/react-native-udp+2.7.0.patch deleted file mode 100644 index 1c15c1d4eb8..00000000000 --- a/patches/react-native-udp+2.7.0.patch +++ /dev/null @@ -1,6594 +0,0 @@ -diff --git a/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSocketClient.java b/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSocketClient.java -index 15d6526..510f06d 100644 ---- a/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSocketClient.java -+++ b/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSocketClient.java -@@ -8,7 +8,7 @@ - package com.tradle.react; - - import android.os.AsyncTask; --import android.support.annotation.Nullable; -+import androidx.annotation.Nullable; - import android.util.Base64; - import android.util.Pair; - -diff --git a/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSockets.java b/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSockets.java -index a0b7c46..2768252 100644 ---- a/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSockets.java -+++ b/node_modules/react-native-udp/android/src/main/java/com/tradle/react/UdpSockets.java -@@ -9,7 +9,7 @@ package com.tradle.react; - - import android.content.Context; - import android.net.wifi.WifiManager; --import android.support.annotation.Nullable; -+import androidx.annotation.Nullable; - import android.util.SparseArray; - import android.os.AsyncTask; - -diff --git a/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h b/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h -deleted file mode 100644 -index b0b244d..0000000 ---- a/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.h -+++ /dev/null -@@ -1,1024 +0,0 @@ --// --// GCDAsyncUdpSocket --// --// This class is in the public domain. --// Originally created by Robbie Hanson of Deusty LLC. --// Updated and maintained by Deusty LLC and the Apple development community. --// --// https://github.com/robbiehanson/CocoaAsyncSocket --// -- --#import --#import --#import --#import -- --NS_ASSUME_NONNULL_BEGIN --extern NSString *const GCDAsyncUdpSocketException; --extern NSString *const GCDAsyncUdpSocketErrorDomain; -- --extern NSString *const GCDAsyncUdpSocketQueueName; --extern NSString *const GCDAsyncUdpSocketThreadName; -- --typedef NS_ENUM(NSInteger, GCDAsyncUdpSocketError) { -- GCDAsyncUdpSocketNoError = 0, // Never used -- GCDAsyncUdpSocketBadConfigError, // Invalid configuration -- GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed -- GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out -- GCDAsyncUdpSocketClosedError, // The socket was closed -- GCDAsyncUdpSocketOtherError, // Description provided in userInfo --}; -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark - --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --@class GCDAsyncUdpSocket; -- --@protocol GCDAsyncUdpSocketDelegate --@optional -- --/** -- * By design, UDP is a connectionless protocol, and connecting is not needed. -- * However, you may optionally choose to connect to a particular host for reasons -- * outlined in the documentation for the various connect methods listed above. -- * -- * This method is called if one of the connect methods are invoked, and the connection is successful. --**/ --- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; -- --/** -- * By design, UDP is a connectionless protocol, and connecting is not needed. -- * However, you may optionally choose to connect to a particular host for reasons -- * outlined in the documentation for the various connect methods listed above. -- * -- * This method is called if one of the connect methods are invoked, and the connection fails. -- * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. --**/ --- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; -- --/** -- * Called when the datagram with the given tag has been sent. --**/ --- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; -- --/** -- * Called if an error occurs while trying to send a datagram. -- * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. --**/ --- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; -- --/** -- * Called when the socket has received the requested datagram. --**/ --- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data -- fromAddress:(NSData *)address -- withFilterContext:(nullable id)filterContext; -- --/** -- * Called when the socket is closed. --**/ --- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; -- --@end -- --/** -- * You may optionally set a receive filter for the socket. -- * A filter can provide several useful features: -- * -- * 1. Many times udp packets need to be parsed. -- * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. -- * The end result is a parallel socket io, datagram parsing, and packet processing. -- * -- * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. -- * The filter can prevent such packets from arriving at the delegate. -- * And because the filter can run in its own independent queue, this doesn't slow down the delegate. -- * -- * - Since the udp protocol does not guarantee delivery, udp packets may be lost. -- * Many protocols built atop udp thus provide various resend/re-request algorithms. -- * This sometimes results in duplicate packets arriving. -- * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. -- * -- * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. -- * Such packets need to be ignored. -- * -- * 3. Sometimes traffic shapers are needed to simulate real world environments. -- * A filter allows you to write custom code to simulate such environments. -- * The ability to code this yourself is especially helpful when your simulated environment -- * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), -- * or the system tools to handle this aren't available (e.g. on a mobile device). -- * -- * @param data - The packet that was received. -- * @param address - The address the data was received from. -- * See utilities section for methods to extract info from address. -- * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. -- * For example, filter block can parse the data and then, -- * pass the parsed data to the delegate. -- * -- * @returns - YES if the received packet should be passed onto the delegate. -- * NO if the received packet should be discarded, and not reported to the delegete. -- * -- * Example: -- * -- * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { -- * -- * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; -- * -- * *context = response; -- * return (response != nil); -- * }; -- * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; -- * --**/ --typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); -- --/** -- * You may optionally set a send filter for the socket. -- * A filter can provide several interesting possibilities: -- * -- * 1. Optional caching of resolved addresses for domain names. -- * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. -- * -- * 2. Reusable modules of code for bandwidth monitoring. -- * -- * 3. Sometimes traffic shapers are needed to simulate real world environments. -- * A filter allows you to write custom code to simulate such environments. -- * The ability to code this yourself is especially helpful when your simulated environment -- * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), -- * or the system tools to handle this aren't available (e.g. on a mobile device). -- * -- * @param data - The packet that was received. -- * @param address - The address the data was received from. -- * See utilities section for methods to extract info from address. -- * @param tag - The tag that was passed in the send method. -- * -- * @returns - YES if the packet should actually be sent over the socket. -- * NO if the packet should be silently dropped (not sent over the socket). -- * -- * Regardless of the return value, the delegate will be informed that the packet was successfully sent. -- * --**/ --typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); -- -- --@interface GCDAsyncUdpSocket : NSObject -- --/** -- * GCDAsyncUdpSocket uses the standard delegate paradigm, -- * but executes all delegate callbacks on a given delegate dispatch queue. -- * This allows for maximum concurrency, while at the same time providing easy thread safety. -- * -- * You MUST set a delegate AND delegate dispatch queue before attempting to -- * use the socket, or you will get an error. -- * -- * The socket queue is optional. -- * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. -- * If you choose to provide a socket queue, the socket queue must not be a concurrent queue, -- * then please see the discussion for the method markSocketQueueTargetQueue. -- * -- * The delegate queue and socket queue can optionally be the same. --**/ --- (instancetype)init; --- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; --- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq; --- (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq; -- --#pragma mark Configuration -- --- (nullable id )delegate; --- (void)setDelegate:(nullable id )delegate; --- (void)synchronouslySetDelegate:(nullable id )delegate; -- --- (nullable dispatch_queue_t)delegateQueue; --- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; --- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; -- --- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; --- (void)setDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; --- (void)synchronouslySetDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; -- --/** -- * By default, both IPv4 and IPv6 are enabled. -- * -- * This means GCDAsyncUdpSocket automatically supports both protocols, -- * and can send to IPv4 or IPv6 addresses, -- * as well as receive over IPv4 and IPv6. -- * -- * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. -- * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. -- * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. -- * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. -- * If IPv4 is preferred, then IPv4 is used. -- * If IPv6 is preferred, then IPv6 is used. -- * If neutral, then the first IP version in the resolved array will be used. -- * -- * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. -- * On prior systems the default IP preference is IPv4. -- **/ --- (BOOL)isIPv4Enabled; --- (void)setIPv4Enabled:(BOOL)flag; -- --- (BOOL)isIPv6Enabled; --- (void)setIPv6Enabled:(BOOL)flag; -- --- (BOOL)isIPv4Preferred; --- (BOOL)isIPv6Preferred; --- (BOOL)isIPVersionNeutral; -- --- (void)setPreferIPv4; --- (void)setPreferIPv6; --- (void)setIPVersionNeutral; -- --/** -- * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. -- * The default maximum size is 65535 bytes. -- * -- * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. -- * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. -- * -- * Since the OS/GCD notifies us of the size of each received UDP packet, -- * the actual allocated buffer size for each packet is exact. -- * And in practice the size of UDP packets is generally much smaller than the max. -- * Indeed most protocols will send and receive packets of only a few bytes, -- * or will set a limit on the size of packets to prevent fragmentation in the IP layer. -- * -- * If you set the buffer size too small, the sockets API in the OS will silently discard -- * any extra data, and you will not be notified of the error. --**/ --- (uint16_t)maxReceiveIPv4BufferSize; --- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; -- --- (uint32_t)maxReceiveIPv6BufferSize; --- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; -- --/** -- * Gets/Sets the maximum size of the buffer that will be allocated for send operations. -- * The default maximum size is 65535 bytes. -- * -- * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be -- * fragmented, and that’s both expensive and risky (if one fragment goes missing, the -- * entire datagram is lost). You are much better off sending a large number of smaller -- * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. -- * -- * You must set it before the sockt is created otherwise it won't work. -- * -- **/ --- (uint16_t)maxSendBufferSize; --- (void)setMaxSendBufferSize:(uint16_t)max; -- --/** -- * User data allows you to associate arbitrary information with the socket. -- * This data is not used internally in any way. --**/ --- (nullable id)userData; --- (void)setUserData:(nullable id)arbitraryUserData; -- --#pragma mark Diagnostics -- --/** -- * Returns the local address info for the socket. -- * -- * The localAddress method returns a sockaddr structure wrapped in a NSData object. -- * The localHost method returns the human readable IP address as a string. -- * -- * Note: Address info may not be available until after the socket has been binded, connected -- * or until after data has been sent. --**/ --- (nullable NSData *)localAddress; --- (nullable NSString *)localHost; --- (uint16_t)localPort; -- --- (nullable NSData *)localAddress_IPv4; --- (nullable NSString *)localHost_IPv4; --- (uint16_t)localPort_IPv4; -- --- (nullable NSData *)localAddress_IPv6; --- (nullable NSString *)localHost_IPv6; --- (uint16_t)localPort_IPv6; -- --/** -- * Returns the remote address info for the socket. -- * -- * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. -- * The connectedHost method returns the human readable IP address as a string. -- * -- * Note: Since UDP is connectionless by design, connected address info -- * will not be available unless the socket is explicitly connected to a remote host/port. -- * If the socket is not connected, these methods will return nil / 0. --**/ --- (nullable NSData *)connectedAddress; --- (nullable NSString *)connectedHost; --- (uint16_t)connectedPort; -- --/** -- * Returns whether or not this socket has been connected to a single host. -- * By design, UDP is a connectionless protocol, and connecting is not needed. -- * If connected, the socket will only be able to send/receive data to/from the connected host. --**/ --- (BOOL)isConnected; -- --/** -- * Returns whether or not this socket has been closed. -- * The only way a socket can be closed is if you explicitly call one of the close methods. --**/ --- (BOOL)isClosed; -- --/** -- * Returns whether or not this socket is IPv4. -- * -- * By default this will be true, unless: -- * - IPv4 is disabled (via setIPv4Enabled:) -- * - The socket is explicitly bound to an IPv6 address -- * - The socket is connected to an IPv6 address --**/ --- (BOOL)isIPv4; -- --/** -- * Returns whether or not this socket is IPv6. -- * -- * By default this will be true, unless: -- * - IPv6 is disabled (via setIPv6Enabled:) -- * - The socket is explicitly bound to an IPv4 address -- * _ The socket is connected to an IPv4 address -- * -- * This method will also return false on platforms that do not support IPv6. -- * Note: The iPhone does not currently support IPv6. --**/ --- (BOOL)isIPv6; -- --#pragma mark Binding -- --/** -- * Binds the UDP socket to the given port. -- * Binding should be done for server sockets that receive data prior to sending it. -- * Client sockets can skip binding, -- * as the OS will automatically assign the socket an available port when it starts sending data. -- * -- * You may optionally pass a port number of zero to immediately bind the socket, -- * yet still allow the OS to automatically assign an available port. -- * -- * You cannot bind a socket after its been connected. -- * You can only bind a socket once. -- * You can still connect a socket (if desired) after binding. -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. --**/ --- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr; -- --/** -- * Binds the UDP socket to the given port and optional interface. -- * Binding should be done for server sockets that receive data prior to sending it. -- * Client sockets can skip binding, -- * as the OS will automatically assign the socket an available port when it starts sending data. -- * -- * You may optionally pass a port number of zero to immediately bind the socket, -- * yet still allow the OS to automatically assign an available port. -- * -- * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). -- * You may also use the special strings "localhost" or "loopback" to specify that -- * the socket only accept packets from the local machine. -- * -- * You cannot bind a socket after its been connected. -- * You can only bind a socket once. -- * You can still connect a socket (if desired) after binding. -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. --**/ --- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; -- --/** -- * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. -- * -- * If you have an existing struct sockaddr you can convert it to a NSData object like so: -- * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; -- * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; -- * -- * Binding should be done for server sockets that receive data prior to sending it. -- * Client sockets can skip binding, -- * as the OS will automatically assign the socket an available port when it starts sending data. -- * -- * You cannot bind a socket after its been connected. -- * You can only bind a socket once. -- * You can still connect a socket (if desired) after binding. -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. --**/ --- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; -- --#pragma mark Connecting -- --/** -- * Connects the UDP socket to the given host and port. -- * By design, UDP is a connectionless protocol, and connecting is not needed. -- * -- * Choosing to connect to a specific host/port has the following effect: -- * - You will only be able to send data to the connected host/port. -- * - You will only be able to receive data from the connected host/port. -- * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". -- * -- * The actual process of connecting a UDP socket does not result in any communication on the socket. -- * It simply changes the internal state of the socket. -- * -- * You cannot bind a socket after it has been connected. -- * You can only connect a socket once. -- * -- * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). -- * -- * This method is asynchronous as it requires a DNS lookup to resolve the given host name. -- * If an obvious error is detected, this method immediately returns NO and sets errPtr. -- * If you don't care about the error, you can pass nil for errPtr. -- * Otherwise, this method returns YES and begins the asynchronous connection process. -- * The result of the asynchronous connection process will be reported via the delegate methods. -- **/ --- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; -- --/** -- * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. -- * -- * If you have an existing struct sockaddr you can convert it to a NSData object like so: -- * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; -- * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; -- * -- * By design, UDP is a connectionless protocol, and connecting is not needed. -- * -- * Choosing to connect to a specific address has the following effect: -- * - You will only be able to send data to the connected address. -- * - You will only be able to receive data from the connected address. -- * - You will receive ICMP messages that come from the connected address, such as "connection refused". -- * -- * Connecting a UDP socket does not result in any communication on the socket. -- * It simply changes the internal state of the socket. -- * -- * You cannot bind a socket after its been connected. -- * You can only connect a socket once. -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. -- * -- * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. -- * Thus when this method returns, the connection has either failed or fully completed. -- * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. -- * However, for compatibility and simplification of delegate code, if this method returns YES -- * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. --**/ --- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; -- --#pragma mark Multicast -- --/** -- * Join multicast group. -- * Group should be an IP address (eg @"225.228.0.1"). -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. --**/ --- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; -- --/** -- * Join multicast group. -- * Group should be an IP address (eg @"225.228.0.1"). -- * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). -- * -- * On success, returns YES. -- * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. --**/ --- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; -- --- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; --- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; -- --#pragma mark Reuse Port -- --/** -- * By default, only one socket can be bound to a given IP address + port at a time. -- * To enable multiple processes to simultaneously bind to the same address+port, -- * you need to enable this functionality in the socket. All processes that wish to -- * use the address+port simultaneously must all enable reuse port on the socket -- * bound to that port. -- **/ --- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; -- --#pragma mark Broadcast -- --/** -- * By default, the underlying socket in the OS will not allow you to send broadcast messages. -- * In order to send broadcast messages, you need to enable this functionality in the socket. -- * -- * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is -- * delivered to every host on the network. -- * The reason this is generally disabled by default (by the OS) is to prevent -- * accidental broadcast messages from flooding the network. --**/ --- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; -- --#pragma mark Sending -- --/** -- * Asynchronously sends the given data, with the given timeout and tag. -- * -- * This method may only be used with a connected socket. -- * Recall that connecting is optional for a UDP socket. -- * For connected sockets, data can only be sent to the connected address. -- * For non-connected sockets, the remote destination is specified for each packet. -- * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. -- * -- * @param data -- * The data to send. -- * If data is nil or zero-length, this method does nothing. -- * If passing NSMutableData, please read the thread-safety notice below. -- * -- * @param timeout -- * The timeout for the send opeartion. -- * If the timeout value is negative, the send operation will not use a timeout. -- * -- * @param tag -- * The tag is for your convenience. -- * It is not sent or received over the socket in any manner what-so-ever. -- * It is reported back as a parameter in the udpSocket:didSendDataWithTag: -- * or udpSocket:didNotSendDataWithTag:dueToError: methods. -- * You can use it as an array index, state id, type constant, etc. -- * -- * -- * Thread-Safety Note: -- * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while -- * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method -- * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying -- * that this particular send operation has completed. -- * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. -- * It simply retains it for performance reasons. -- * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. -- * Copying this data adds an unwanted/unneeded overhead. -- * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket -- * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time -- * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. --**/ --- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; -- --/** -- * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. -- * -- * This method cannot be used with a connected socket. -- * Recall that connecting is optional for a UDP socket. -- * For connected sockets, data can only be sent to the connected address. -- * For non-connected sockets, the remote destination is specified for each packet. -- * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. -- * -- * @param data -- * The data to send. -- * If data is nil or zero-length, this method does nothing. -- * If passing NSMutableData, please read the thread-safety notice below. -- * -- * @param host -- * The destination to send the udp packet to. -- * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). -- * You may also use the convenience strings of "loopback" or "localhost". -- * -- * @param port -- * The port of the host to send to. -- * -- * @param timeout -- * The timeout for the send opeartion. -- * If the timeout value is negative, the send operation will not use a timeout. -- * -- * @param tag -- * The tag is for your convenience. -- * It is not sent or received over the socket in any manner what-so-ever. -- * It is reported back as a parameter in the udpSocket:didSendDataWithTag: -- * or udpSocket:didNotSendDataWithTag:dueToError: methods. -- * You can use it as an array index, state id, type constant, etc. -- * -- * -- * Thread-Safety Note: -- * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while -- * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method -- * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying -- * that this particular send operation has completed. -- * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. -- * It simply retains it for performance reasons. -- * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. -- * Copying this data adds an unwanted/unneeded overhead. -- * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket -- * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time -- * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. --**/ --- (void)sendData:(NSData *)data -- toHost:(NSString *)host -- port:(uint16_t)port -- withTimeout:(NSTimeInterval)timeout -- tag:(long)tag; -- --/** -- * Asynchronously sends the given data, with the given timeout and tag, to the given address. -- * -- * This method cannot be used with a connected socket. -- * Recall that connecting is optional for a UDP socket. -- * For connected sockets, data can only be sent to the connected address. -- * For non-connected sockets, the remote destination is specified for each packet. -- * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. -- * -- * @param data -- * The data to send. -- * If data is nil or zero-length, this method does nothing. -- * If passing NSMutableData, please read the thread-safety notice below. -- * -- * @param remoteAddr -- * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). -- * -- * @param timeout -- * The timeout for the send opeartion. -- * If the timeout value is negative, the send operation will not use a timeout. -- * -- * @param tag -- * The tag is for your convenience. -- * It is not sent or received over the socket in any manner what-so-ever. -- * It is reported back as a parameter in the udpSocket:didSendDataWithTag: -- * or udpSocket:didNotSendDataWithTag:dueToError: methods. -- * You can use it as an array index, state id, type constant, etc. -- * -- * -- * Thread-Safety Note: -- * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while -- * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method -- * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying -- * that this particular send operation has completed. -- * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. -- * It simply retains it for performance reasons. -- * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. -- * Copying this data adds an unwanted/unneeded overhead. -- * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket -- * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time -- * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. --**/ --- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; -- --/** -- * You may optionally set a send filter for the socket. -- * A filter can provide several interesting possibilities: -- * -- * 1. Optional caching of resolved addresses for domain names. -- * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. -- * -- * 2. Reusable modules of code for bandwidth monitoring. -- * -- * 3. Sometimes traffic shapers are needed to simulate real world environments. -- * A filter allows you to write custom code to simulate such environments. -- * The ability to code this yourself is especially helpful when your simulated environment -- * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), -- * or the system tools to handle this aren't available (e.g. on a mobile device). -- * -- * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. -- * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. -- * -- * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), -- * passing YES for the isAsynchronous parameter. --**/ --- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; -- --/** -- * The receive filter can be run via dispatch_async or dispatch_sync. -- * Most typical situations call for asynchronous operation. -- * -- * However, there are a few situations in which synchronous operation is preferred. -- * Such is the case when the filter is extremely minimal and fast. -- * This is because dispatch_sync is faster than dispatch_async. -- * -- * If you choose synchronous operation, be aware of possible deadlock conditions. -- * Since the socket queue is executing your block via dispatch_sync, -- * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. -- * For example, you can't query properties on the socket. --**/ --- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock -- withQueue:(nullable dispatch_queue_t)filterQueue -- isAsynchronous:(BOOL)isAsynchronous; -- --#pragma mark Receiving -- --/** -- * There are two modes of operation for receiving packets: one-at-a-time & continuous. -- * -- * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. -- * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, -- * where your state machine may not always be ready to process incoming packets. -- * -- * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. -- * Receiving packets continuously is better suited to real-time streaming applications. -- * -- * You may switch back and forth between one-at-a-time mode and continuous mode. -- * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. -- * -- * When a packet is received (and not filtered by the optional receive filter), -- * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. -- * -- * If the socket is able to begin receiving packets, this method returns YES. -- * Otherwise it returns NO, and sets the errPtr with appropriate error information. -- * -- * An example error: -- * You created a udp socket to act as a server, and immediately called receive. -- * You forgot to first bind the socket to a port number, and received a error with a message like: -- * "Must bind socket before you can receive data." --**/ --- (BOOL)receiveOnce:(NSError **)errPtr; -- --/** -- * There are two modes of operation for receiving packets: one-at-a-time & continuous. -- * -- * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. -- * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, -- * where your state machine may not always be ready to process incoming packets. -- * -- * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. -- * Receiving packets continuously is better suited to real-time streaming applications. -- * -- * You may switch back and forth between one-at-a-time mode and continuous mode. -- * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. -- * -- * For every received packet (not filtered by the optional receive filter), -- * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. -- * -- * If the socket is able to begin receiving packets, this method returns YES. -- * Otherwise it returns NO, and sets the errPtr with appropriate error information. -- * -- * An example error: -- * You created a udp socket to act as a server, and immediately called receive. -- * You forgot to first bind the socket to a port number, and received a error with a message like: -- * "Must bind socket before you can receive data." --**/ --- (BOOL)beginReceiving:(NSError **)errPtr; -- --/** -- * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. -- * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. -- * -- * Important Note: -- * GCDAsyncUdpSocket may be running in parallel with your code. -- * That is, your delegate is likely running on a separate thread/dispatch_queue. -- * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. -- * Thus, if those delegate methods have already been dispatch_async'd, -- * your didReceive delegate method may still be invoked after this method has been called. -- * You should be aware of this, and program defensively. --**/ --- (void)pauseReceiving; -- --/** -- * You may optionally set a receive filter for the socket. -- * This receive filter may be set to run in its own queue (independent of delegate queue). -- * -- * A filter can provide several useful features. -- * -- * 1. Many times udp packets need to be parsed. -- * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. -- * The end result is a parallel socket io, datagram parsing, and packet processing. -- * -- * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. -- * The filter can prevent such packets from arriving at the delegate. -- * And because the filter can run in its own independent queue, this doesn't slow down the delegate. -- * -- * - Since the udp protocol does not guarantee delivery, udp packets may be lost. -- * Many protocols built atop udp thus provide various resend/re-request algorithms. -- * This sometimes results in duplicate packets arriving. -- * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. -- * -- * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. -- * Such packets need to be ignored. -- * -- * 3. Sometimes traffic shapers are needed to simulate real world environments. -- * A filter allows you to write custom code to simulate such environments. -- * The ability to code this yourself is especially helpful when your simulated environment -- * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), -- * or the system tools to handle this aren't available (e.g. on a mobile device). -- * -- * Example: -- * -- * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { -- * -- * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; -- * -- * *context = response; -- * return (response != nil); -- * }; -- * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; -- * -- * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. -- * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. -- * -- * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), -- * passing YES for the isAsynchronous parameter. --**/ --- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; -- --/** -- * The receive filter can be run via dispatch_async or dispatch_sync. -- * Most typical situations call for asynchronous operation. -- * -- * However, there are a few situations in which synchronous operation is preferred. -- * Such is the case when the filter is extremely minimal and fast. -- * This is because dispatch_sync is faster than dispatch_async. -- * -- * If you choose synchronous operation, be aware of possible deadlock conditions. -- * Since the socket queue is executing your block via dispatch_sync, -- * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. -- * For example, you can't query properties on the socket. --**/ --- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock -- withQueue:(nullable dispatch_queue_t)filterQueue -- isAsynchronous:(BOOL)isAsynchronous; -- --#pragma mark Closing -- --/** -- * Immediately closes the underlying socket. -- * Any pending send operations are discarded. -- * -- * The GCDAsyncUdpSocket instance may optionally be used again. -- * (it will setup/configure/use another unnderlying BSD socket). --**/ --- (void)close; -- --/** -- * Closes the underlying socket after all pending send operations have been sent. -- * -- * The GCDAsyncUdpSocket instance may optionally be used again. -- * (it will setup/configure/use another unnderlying BSD socket). --**/ --- (void)closeAfterSending; -- --#pragma mark Advanced --/** -- * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. -- * In most cases, the instance creates this queue itself. -- * However, to allow for maximum flexibility, the internal queue may be passed in the init method. -- * This allows for some advanced options such as controlling socket priority via target queues. -- * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. -- * -- * For example, imagine there are 2 queues: -- * dispatch_queue_t socketQueue; -- * dispatch_queue_t socketTargetQueue; -- * -- * If you do this (pseudo-code): -- * socketQueue.targetQueue = socketTargetQueue; -- * -- * Then all socketQueue operations will actually get run on the given socketTargetQueue. -- * This is fine and works great in most situations. -- * But if you run code directly from within the socketTargetQueue that accesses the socket, -- * you could potentially get deadlock. Imagine the following code: -- * -- * - (BOOL)socketHasSomething -- * { -- * __block BOOL result = NO; -- * dispatch_block_t block = ^{ -- * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; -- * } -- * if (is_executing_on_queue(socketQueue)) -- * block(); -- * else -- * dispatch_sync(socketQueue, block); -- * -- * return result; -- * } -- * -- * What happens if you call this method from the socketTargetQueue? The result is deadlock. -- * This is because the GCD API offers no mechanism to discover a queue's targetQueue. -- * Thus we have no idea if our socketQueue is configured with a targetQueue. -- * If we had this information, we could easily avoid deadlock. -- * But, since these API's are missing or unfeasible, you'll have to explicitly set it. -- * -- * IF you pass a socketQueue via the init method, -- * AND you've configured the passed socketQueue with a targetQueue, -- * THEN you should pass the end queue in the target hierarchy. -- * -- * For example, consider the following queue hierarchy: -- * socketQueue -> ipQueue -> moduleQueue -- * -- * This example demonstrates priority shaping within some server. -- * All incoming client connections from the same IP address are executed on the same target queue. -- * And all connections for a particular module are executed on the same target queue. -- * Thus, the priority of all networking for the entire module can be changed on the fly. -- * Additionally, networking traffic from a single IP cannot monopolize the module. -- * -- * Here's how you would accomplish something like that: -- * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock -- * { -- * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); -- * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; -- * -- * dispatch_set_target_queue(socketQueue, ipQueue); -- * dispatch_set_target_queue(iqQueue, moduleQueue); -- * -- * return socketQueue; -- * } -- * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket -- * { -- * [clientConnections addObject:newSocket]; -- * [newSocket markSocketQueueTargetQueue:moduleQueue]; -- * } -- * -- * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. -- * This is often NOT the case, as such queues are used solely for execution shaping. -- **/ --- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; --- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; -- --/** -- * It's not thread-safe to access certain variables from outside the socket's internal queue. -- * -- * For example, the socket file descriptor. -- * File descriptors are simply integers which reference an index in the per-process file table. -- * However, when one requests a new file descriptor (by opening a file or socket), -- * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. -- * So if we're not careful, the following could be possible: -- * -- * - Thread A invokes a method which returns the socket's file descriptor. -- * - The socket is closed via the socket's internal queue on thread B. -- * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. -- * - Thread A is now accessing/altering the file instead of the socket. -- * -- * In addition to this, other variables are not actually objects, -- * and thus cannot be retained/released or even autoreleased. -- * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. -- * -- * Although there are internal variables that make it difficult to maintain thread-safety, -- * it is important to provide access to these variables -- * to ensure this class can be used in a wide array of environments. -- * This method helps to accomplish this by invoking the current block on the socket's internal queue. -- * The methods below can be invoked from within the block to access -- * those generally thread-unsafe internal variables in a thread-safe manner. -- * The given block will be invoked synchronously on the socket's internal queue. -- * -- * If you save references to any protected variables and use them outside the block, you do so at your own peril. --**/ --- (void)performBlock:(dispatch_block_t)block; -- --/** -- * These methods are only available from within the context of a performBlock: invocation. -- * See the documentation for the performBlock: method above. -- * -- * Provides access to the socket's file descriptor(s). -- * If the socket isn't connected, or explicity bound to a particular interface, -- * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. --**/ --- (int)socketFD; --- (int)socket4FD; --- (int)socket6FD; -- --#if TARGET_OS_IPHONE -- --/** -- * These methods are only available from within the context of a performBlock: invocation. -- * See the documentation for the performBlock: method above. -- * -- * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. -- * -- * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) -- * However, if you need one for any reason, -- * these methods are a convenient way to get access to a safe instance of one. --**/ --- (nullable CFReadStreamRef)readStream; --- (nullable CFWriteStreamRef)writeStream; -- --/** -- * This method is only available from within the context of a performBlock: invocation. -- * See the documentation for the performBlock: method above. -- * -- * Configures the socket to allow it to operate when the iOS application has been backgrounded. -- * In other words, this method creates a read & write stream, and invokes: -- * -- * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -- * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); -- * -- * Returns YES if successful, NO otherwise. -- * -- * Example usage: -- * -- * [asyncUdpSocket performBlock:^{ -- * [asyncUdpSocket enableBackgroundingOnSocket]; -- * }]; -- * -- * -- * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). --**/ --//- (BOOL)enableBackgroundingOnSockets; -- --#endif -- --#pragma mark Utilities -- --/** -- * Extracting host/port/family information from raw address data. --**/ -- --+ (nullable NSString *)hostFromAddress:(NSData *)address; --+ (uint16_t)portFromAddress:(NSData *)address; --+ (int)familyFromAddress:(NSData *)address; -- --+ (BOOL)isIPv4Address:(NSData *)address; --+ (BOOL)isIPv6Address:(NSData *)address; -- --+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; --+ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; -- --@end -- --NS_ASSUME_NONNULL_END -diff --git a/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m b/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m -deleted file mode 100644 -index 58a5fdd..0000000 ---- a/node_modules/react-native-udp/ios/CocoaAsyncSocket/GCDAsyncUdpSocket.m -+++ /dev/null -@@ -1,5508 +0,0 @@ --// --// GCDAsyncUdpSocket --// --// This class is in the public domain. --// Originally created by Robbie Hanson of Deusty LLC. --// Updated and maintained by Deusty LLC and the Apple development community. --// --// https://github.com/robbiehanson/CocoaAsyncSocket --// -- --#import "GCDAsyncUdpSocket.h" -- --#if ! __has_feature(objc_arc) --#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). --// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC --#endif -- --#if TARGET_OS_IPHONE -- #import -- #import --#endif -- --#import --#import --#import --#import --#import --#import --#import -- -- --#if 0 -- --// Logging Enabled - See log level below -- --// Logging uses the CocoaLumberjack framework (which is also GCD based). --// http://code.google.com/p/cocoalumberjack/ --// --// It allows us to do a lot of logging without significantly slowing down the code. --#import "DDLog.h" -- --#define LogAsync NO --#define LogContext 65535 -- --#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) --#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__) -- --#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -- --#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) --#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__) -- --#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD) --#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__) -- --// Log levels : off, error, warn, info, verbose --static const int logLevel = LOG_LEVEL_VERBOSE; -- --#else -- --// Logging Disabled -- --#define LogError(frmt, ...) {} --#define LogWarn(frmt, ...) {} --#define LogInfo(frmt, ...) {} --#define LogVerbose(frmt, ...) {} -- --#define LogCError(frmt, ...) {} --#define LogCWarn(frmt, ...) {} --#define LogCInfo(frmt, ...) {} --#define LogCVerbose(frmt, ...) {} -- --#define LogTrace() {} --#define LogCTrace(frmt, ...) {} -- --#endif -- --/** -- * Seeing a return statements within an inner block -- * can sometimes be mistaken for a return point of the enclosing method. -- * This makes inline blocks a bit easier to read. --**/ --#define return_from_block return -- --/** -- * A socket file descriptor is really just an integer. -- * It represents the index of the socket within the kernel. -- * This makes invalid file descriptor comparisons easier to read. --**/ --#define SOCKET_NULL -1 -- --/** -- * Just to type less code. --**/ --#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} -- -- --@class GCDAsyncUdpSendPacket; -- --NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException"; --NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain"; -- --NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket"; --NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream"; -- --enum GCDAsyncUdpSocketFlags --{ -- kDidCreateSockets = 1 << 0, // If set, the sockets have been created. -- kDidBind = 1 << 1, // If set, bind has been called. -- kConnecting = 1 << 2, // If set, a connection attempt is in progress. -- kDidConnect = 1 << 3, // If set, socket is connected. -- kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled -- kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled -- kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6. -- kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4. -- kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended. -- kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended. -- kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended. -- kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended. -- kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown. -- kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown. -- kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued. -- kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued. -- kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets. --#if TARGET_OS_IPHONE -- kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread --#endif --}; -- --enum GCDAsyncUdpSocketConfig --{ -- kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled -- kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled -- kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6 -- kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4 --}; -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark - --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --@interface GCDAsyncUdpSocket () --{ --#if __has_feature(objc_arc_weak) -- __weak id delegate; --#else -- __unsafe_unretained id delegate; --#endif -- dispatch_queue_t delegateQueue; -- -- GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock; -- dispatch_queue_t receiveFilterQueue; -- BOOL receiveFilterAsync; -- -- GCDAsyncUdpSocketSendFilterBlock sendFilterBlock; -- dispatch_queue_t sendFilterQueue; -- BOOL sendFilterAsync; -- -- uint32_t flags; -- uint16_t config; -- -- uint16_t max4ReceiveSize; -- uint32_t max6ReceiveSize; -- -- uint16_t maxSendSize; -- -- int socket4FD; -- int socket6FD; -- -- dispatch_queue_t socketQueue; -- -- dispatch_source_t send4Source; -- dispatch_source_t send6Source; -- dispatch_source_t receive4Source; -- dispatch_source_t receive6Source; -- dispatch_source_t sendTimer; -- -- GCDAsyncUdpSendPacket *currentSend; -- NSMutableArray *sendQueue; -- -- unsigned long socket4FDBytesAvailable; -- unsigned long socket6FDBytesAvailable; -- -- uint32_t pendingFilterOperations; -- -- NSData *cachedLocalAddress4; -- NSString *cachedLocalHost4; -- uint16_t cachedLocalPort4; -- -- NSData *cachedLocalAddress6; -- NSString *cachedLocalHost6; -- uint16_t cachedLocalPort6; -- -- NSData *cachedConnectedAddress; -- NSString *cachedConnectedHost; -- uint16_t cachedConnectedPort; -- int cachedConnectedFamily; -- -- void *IsOnSocketQueueOrTargetQueueKey; -- --#if TARGET_OS_IPHONE -- CFStreamClientContext streamContext; -- CFReadStreamRef readStream4; -- CFReadStreamRef readStream6; -- CFWriteStreamRef writeStream4; -- CFWriteStreamRef writeStream6; --#endif -- -- id userData; --} -- --- (void)resumeSend4Source; --- (void)resumeSend6Source; --- (void)resumeReceive4Source; --- (void)resumeReceive6Source; --- (void)closeSockets; -- --- (void)maybeConnect; --- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr; --- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr; -- --- (void)maybeDequeueSend; --- (void)doPreSend; --- (void)doSend; --- (void)endCurrentSend; --- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout; -- --- (void)doReceive; --- (void)doReceiveEOF; -- --- (void)closeWithError:(NSError *)error; -- --- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr; -- --#if TARGET_OS_IPHONE --- (BOOL)createReadAndWriteStreams:(NSError **)errPtr; --- (BOOL)registerForStreamCallbacks:(NSError **)errPtr; --- (BOOL)addStreamsToRunLoop:(NSError **)errPtr; --- (BOOL)openStreams:(NSError **)errPtr; --- (void)removeStreamsFromRunLoop; --- (void)closeReadAndWriteStreams; --#endif -- --+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; --+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; --+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4; --+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6; -- --#if TARGET_OS_IPHONE --// Forward declaration --+ (void)listenerThread; --#endif -- --@end -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark - --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --/** -- * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write. --**/ --@interface GCDAsyncUdpSendPacket : NSObject { --@public -- NSData *buffer; -- NSTimeInterval timeout; -- long tag; -- -- BOOL resolveInProgress; -- BOOL filterInProgress; -- -- NSArray *resolvedAddresses; -- NSError *resolveError; -- -- NSData *address; -- int addressFamily; --} -- --- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; -- --@end -- --@implementation GCDAsyncUdpSendPacket -- --- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i --{ -- if ((self = [super init])) -- { -- buffer = d; -- timeout = t; -- tag = i; -- -- resolveInProgress = NO; -- } -- return self; --} -- -- --@end -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark - --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --@interface GCDAsyncUdpSpecialPacket : NSObject { --@public --// uint8_t type; -- -- BOOL resolveInProgress; -- -- NSArray *addresses; -- NSError *error; --} -- --- (id)init; -- --@end -- --@implementation GCDAsyncUdpSpecialPacket -- --- (id)init --{ -- self = [super init]; -- return self; --} -- -- --@end -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark - --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --@implementation GCDAsyncUdpSocket -- --- (id)init --{ -- LogTrace(); -- -- return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL]; --} -- --- (id)initWithSocketQueue:(dispatch_queue_t)sq --{ -- LogTrace(); -- -- return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq]; --} -- --- (id)initWithDelegate:(id )aDelegate delegateQueue:(dispatch_queue_t)dq --{ -- LogTrace(); -- -- return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL]; --} -- --- (id)initWithDelegate:(id )aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq --{ -- LogTrace(); -- -- if ((self = [super init])) -- { -- delegate = aDelegate; -- -- if (dq) -- { -- delegateQueue = dq; -- #if !OS_OBJECT_USE_OBJC -- dispatch_retain(delegateQueue); -- #endif -- } -- -- max4ReceiveSize = 65535; -- max6ReceiveSize = 65535; -- -- maxSendSize = 65535; -- -- socket4FD = SOCKET_NULL; -- socket6FD = SOCKET_NULL; -- -- if (sq) -- { -- NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), -- @"The given socketQueue parameter must not be a concurrent queue."); -- NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), -- @"The given socketQueue parameter must not be a concurrent queue."); -- NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), -- @"The given socketQueue parameter must not be a concurrent queue."); -- -- socketQueue = sq; -- #if !OS_OBJECT_USE_OBJC -- dispatch_retain(socketQueue); -- #endif -- } -- else -- { -- socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL); -- } -- -- // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter. -- // From the documentation: -- // -- // > Keys are only compared as pointers and are never dereferenced. -- // > Thus, you can use a pointer to a static variable for a specific subsystem or -- // > any other value that allows you to identify the value uniquely. -- // -- // We're just going to use the memory address of an ivar. -- // Specifically an ivar that is explicitly named for our purpose to make the code more readable. -- // -- // However, it feels tedious (and less readable) to include the "&" all the time: -- // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey) -- // -- // So we're going to make it so it doesn't matter if we use the '&' or not, -- // by assigning the value of the ivar to the address of the ivar. -- // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey; -- -- IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey; -- -- void *nonNullUnusedPointer = (__bridge void *)self; -- dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); -- -- currentSend = nil; -- sendQueue = [[NSMutableArray alloc] initWithCapacity:5]; -- -- #if TARGET_OS_IPHONE -- [[NSNotificationCenter defaultCenter] addObserver:self -- selector:@selector(applicationWillEnterForeground:) -- name:UIApplicationWillEnterForegroundNotification -- object:nil]; -- #endif -- } -- return self; --} -- --- (void)dealloc --{ -- LogInfo(@"%@ - %@ (start)", THIS_METHOD, self); -- --#if TARGET_OS_IPHONE -- [[NSNotificationCenter defaultCenter] removeObserver:self]; --#endif -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- [self closeWithError:nil]; -- } -- else -- { -- dispatch_sync(socketQueue, ^{ -- [self closeWithError:nil]; -- }); -- } -- -- delegate = nil; -- #if !OS_OBJECT_USE_OBJC -- if (delegateQueue) dispatch_release(delegateQueue); -- #endif -- delegateQueue = NULL; -- -- #if !OS_OBJECT_USE_OBJC -- if (socketQueue) dispatch_release(socketQueue); -- #endif -- socketQueue = NULL; -- -- LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self); --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Configuration --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (id)delegate --{ -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- return delegate; -- } -- else -- { -- __block id result = nil; -- -- dispatch_sync(socketQueue, ^{ -- result = delegate; -- }); -- -- return result; -- } --} -- --- (void)setDelegate:(id )newDelegate synchronously:(BOOL)synchronously --{ -- dispatch_block_t block = ^{ -- delegate = newDelegate; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { -- block(); -- } -- else { -- if (synchronously) -- dispatch_sync(socketQueue, block); -- else -- dispatch_async(socketQueue, block); -- } --} -- --- (void)setDelegate:(id )newDelegate --{ -- [self setDelegate:newDelegate synchronously:NO]; --} -- --- (void)synchronouslySetDelegate:(id )newDelegate --{ -- [self setDelegate:newDelegate synchronously:YES]; --} -- --- (dispatch_queue_t)delegateQueue --{ -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- return delegateQueue; -- } -- else -- { -- __block dispatch_queue_t result = NULL; -- -- dispatch_sync(socketQueue, ^{ -- result = delegateQueue; -- }); -- -- return result; -- } --} -- --- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously --{ -- dispatch_block_t block = ^{ -- -- #if !OS_OBJECT_USE_OBJC -- if (delegateQueue) dispatch_release(delegateQueue); -- if (newDelegateQueue) dispatch_retain(newDelegateQueue); -- #endif -- -- delegateQueue = newDelegateQueue; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { -- block(); -- } -- else { -- if (synchronously) -- dispatch_sync(socketQueue, block); -- else -- dispatch_async(socketQueue, block); -- } --} -- --- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue --{ -- [self setDelegateQueue:newDelegateQueue synchronously:NO]; --} -- --- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue --{ -- [self setDelegateQueue:newDelegateQueue synchronously:YES]; --} -- --- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr --{ -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- if (delegatePtr) *delegatePtr = delegate; -- if (delegateQueuePtr) *delegateQueuePtr = delegateQueue; -- } -- else -- { -- __block id dPtr = NULL; -- __block dispatch_queue_t dqPtr = NULL; -- -- dispatch_sync(socketQueue, ^{ -- dPtr = delegate; -- dqPtr = delegateQueue; -- }); -- -- if (delegatePtr) *delegatePtr = dPtr; -- if (delegateQueuePtr) *delegateQueuePtr = dqPtr; -- } --} -- --- (void)setDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously --{ -- dispatch_block_t block = ^{ -- -- delegate = newDelegate; -- -- #if !OS_OBJECT_USE_OBJC -- if (delegateQueue) dispatch_release(delegateQueue); -- if (newDelegateQueue) dispatch_retain(newDelegateQueue); -- #endif -- -- delegateQueue = newDelegateQueue; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) { -- block(); -- } -- else { -- if (synchronously) -- dispatch_sync(socketQueue, block); -- else -- dispatch_async(socketQueue, block); -- } --} -- --- (void)setDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue --{ -- [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO]; --} -- --- (void)synchronouslySetDelegate:(id )newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue --{ -- [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES]; --} -- --- (BOOL)isIPv4Enabled --{ -- // Note: YES means kIPv4Disabled is OFF -- -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- -- result = ((config & kIPv4Disabled) == 0); -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setIPv4Enabled:(BOOL)flag --{ -- // Note: YES means kIPv4Disabled is OFF -- -- dispatch_block_t block = ^{ -- -- LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); -- -- if (flag) -- config &= ~kIPv4Disabled; -- else -- config |= kIPv4Disabled; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (BOOL)isIPv6Enabled --{ -- // Note: YES means kIPv6Disabled is OFF -- -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- -- result = ((config & kIPv6Disabled) == 0); -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setIPv6Enabled:(BOOL)flag --{ -- // Note: YES means kIPv6Disabled is OFF -- -- dispatch_block_t block = ^{ -- -- LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO")); -- -- if (flag) -- config &= ~kIPv6Disabled; -- else -- config |= kIPv6Disabled; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (BOOL)isIPv4Preferred --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- result = (config & kPreferIPv4) ? YES : NO; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (BOOL)isIPv6Preferred --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- result = (config & kPreferIPv6) ? YES : NO; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (BOOL)isIPVersionNeutral --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- result = (config & (kPreferIPv4 | kPreferIPv6)) == 0; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setPreferIPv4 --{ -- dispatch_block_t block = ^{ -- -- LogTrace(); -- -- config |= kPreferIPv4; -- config &= ~kPreferIPv6; -- -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)setPreferIPv6 --{ -- dispatch_block_t block = ^{ -- -- LogTrace(); -- -- config &= ~kPreferIPv4; -- config |= kPreferIPv6; -- -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)setIPVersionNeutral --{ -- dispatch_block_t block = ^{ -- -- LogTrace(); -- -- config &= ~kPreferIPv4; -- config &= ~kPreferIPv6; -- -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (uint16_t)maxReceiveIPv4BufferSize --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- result = max4ReceiveSize; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max --{ -- dispatch_block_t block = ^{ -- -- LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); -- -- max4ReceiveSize = max; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (uint32_t)maxReceiveIPv6BufferSize --{ -- __block uint32_t result = 0; -- -- dispatch_block_t block = ^{ -- -- result = max6ReceiveSize; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max --{ -- dispatch_block_t block = ^{ -- -- LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); -- -- max6ReceiveSize = max; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)setMaxSendBufferSize:(uint16_t)max --{ -- dispatch_block_t block = ^{ -- -- LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max); -- -- maxSendSize = max; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (uint16_t)maxSendBufferSize --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- result = maxSendSize; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (id)userData --{ -- __block id result = nil; -- -- dispatch_block_t block = ^{ -- -- result = userData; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (void)setUserData:(id)arbitraryUserData --{ -- dispatch_block_t block = ^{ -- -- if (userData != arbitraryUserData) -- { -- userData = arbitraryUserData; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Delegate Helpers --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (void)notifyDidConnectToAddress:(NSData *)anAddress --{ -- LogTrace(); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)]) -- { -- NSData *address = [anAddress copy]; // In case param is NSMutableData -- -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocket:self didConnectToAddress:address]; -- }}); -- } --} -- --- (void)notifyDidNotConnect:(NSError *)error --{ -- LogTrace(); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)]) -- { -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocket:self didNotConnect:error]; -- }}); -- } --} -- --- (void)notifyDidSendDataWithTag:(long)tag --{ -- LogTrace(); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)]) -- { -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocket:self didSendDataWithTag:tag]; -- }}); -- } --} -- --- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error --{ -- LogTrace(); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)]) -- { -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error]; -- }}); -- } --} -- --- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context --{ -- LogTrace(); -- -- SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:selector]) -- { -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context]; -- }}); -- } --} -- --- (void)notifyDidCloseWithError:(NSError *)error --{ -- LogTrace(); -- -- __strong id theDelegate = delegate; -- if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)]) -- { -- dispatch_async(delegateQueue, ^{ @autoreleasepool { -- -- [theDelegate udpSocketDidClose:self withError:error]; -- }}); -- } --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Errors --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (NSError *)badConfigError:(NSString *)errMsg --{ -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain -- code:GCDAsyncUdpSocketBadConfigError -- userInfo:userInfo]; --} -- --- (NSError *)badParamError:(NSString *)errMsg --{ -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain -- code:GCDAsyncUdpSocketBadParamError -- userInfo:userInfo]; --} -- --- (NSError *)gaiError:(int)gai_error --{ -- NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding]; -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo]; --} -- --- (NSError *)errnoErrorWithReason:(NSString *)reason --{ -- NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)]; -- NSDictionary *userInfo; -- -- if (reason) -- userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, -- reason, NSLocalizedFailureReasonErrorKey, nil]; -- else -- userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, nil]; -- -- return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; --} -- --- (NSError *)errnoError --{ -- return [self errnoErrorWithReason:nil]; --} -- --/** -- * Returns a standard send timeout error. --**/ --- (NSError *)sendTimeoutError --{ -- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError", -- @"GCDAsyncUdpSocket", [NSBundle mainBundle], -- @"Send operation timed out", nil); -- -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain -- code:GCDAsyncUdpSocketSendTimeoutError -- userInfo:userInfo]; --} -- --- (NSError *)socketClosedError --{ -- NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError", -- @"GCDAsyncUdpSocket", [NSBundle mainBundle], -- @"Socket closed", nil); -- -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo]; --} -- --- (NSError *)otherError:(NSString *)errMsg --{ -- NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; -- -- return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain -- code:GCDAsyncUdpSocketOtherError -- userInfo:userInfo]; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Utilities --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)preOp:(NSError **)errPtr --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if (delegate == nil) // Must have delegate set -- { -- if (errPtr) -- { -- NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- if (delegateQueue == NULL) // Must have delegate queue set -- { -- if (errPtr) -- { -- NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- return YES; --} -- --/** -- * This method executes on a global concurrent queue. -- * When complete, it executes the given completion block on the socketQueue. --**/ --- (void)asyncResolveHost:(NSString *)aHost -- port:(uint16_t)port -- withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock --{ -- LogTrace(); -- -- // Check parameter(s) -- -- if (aHost == nil) -- { -- NSString *msg = @"The host param is nil. Should be domain name or IP address string."; -- NSError *error = [self badParamError:msg]; -- -- // We should still use dispatch_async since this method is expected to be asynchronous -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- completionBlock(nil, error); -- }}); -- -- return; -- } -- -- // It's possible that the given aHost parameter is actually a NSMutableString. -- // So we want to copy it now, within this block that will be executed synchronously. -- // This way the asynchronous lookup block below doesn't have to worry about it changing. -- -- NSString *host = [aHost copy]; -- -- -- dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); -- dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool { -- -- NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2]; -- NSError *error = nil; -- -- if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"]) -- { -- // Use LOOPBACK address -- struct sockaddr_in sockaddr4; -- memset(&sockaddr4, 0, sizeof(sockaddr4)); -- -- sockaddr4.sin_len = sizeof(struct sockaddr_in); -- sockaddr4.sin_family = AF_INET; -- sockaddr4.sin_port = htons(port); -- sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); -- -- struct sockaddr_in6 sockaddr6; -- memset(&sockaddr6, 0, sizeof(sockaddr6)); -- -- sockaddr6.sin6_len = sizeof(struct sockaddr_in6); -- sockaddr6.sin6_family = AF_INET6; -- sockaddr6.sin6_port = htons(port); -- sockaddr6.sin6_addr = in6addr_loopback; -- -- // Wrap the native address structures and add to list -- [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]]; -- [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]]; -- } -- else -- { -- NSString *portStr = [NSString stringWithFormat:@"%hu", port]; -- -- struct addrinfo hints, *res, *res0; -- -- memset(&hints, 0, sizeof(hints)); -- hints.ai_family = PF_UNSPEC; -- hints.ai_socktype = SOCK_DGRAM; -- hints.ai_protocol = IPPROTO_UDP; -- -- int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0); -- -- if (gai_error) -- { -- error = [self gaiError:gai_error]; -- } -- else -- { -- for(res = res0; res; res = res->ai_next) -- { -- if (res->ai_family == AF_INET) -- { -- // Found IPv4 address -- // Wrap the native address structure and add to list -- -- [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; -- } -- else if (res->ai_family == AF_INET6) -- { -- -- // Fixes connection issues with IPv6, it is the same solution for udp socket. -- // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158 -- struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr; -- in_port_t *portPtr = &sockaddr->sin6_port; -- if ((portPtr != NULL) && (*portPtr == 0)) { -- *portPtr = htons(port); -- } -- -- // Found IPv6 address -- // Wrap the native address structure and add to list -- [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]]; -- } -- } -- freeaddrinfo(res0); -- -- if ([addresses count] == 0) -- { -- error = [self gaiError:EAI_FAIL]; -- } -- } -- } -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- completionBlock(addresses, error); -- }}); -- -- }}); --} -- --/** -- * This method picks an address from the given list of addresses. -- * The address picked depends upon which protocols are disabled, deactived, & preferred. -- * -- * Returns the address family (AF_INET or AF_INET6) of the picked address, -- * or AF_UNSPEC and the corresponding error is there's a problem. --**/ --- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert([addresses count] > 0, @"Expected at least one address"); -- -- int resultAF = AF_UNSPEC; -- NSData *resultAddress = nil; -- NSError *resultError = nil; -- -- // Check for problems -- -- BOOL resolvedIPv4Address = NO; -- BOOL resolvedIPv6Address = NO; -- -- for (NSData *address in addresses) -- { -- switch ([[self class] familyFromAddress:address]) -- { -- case AF_INET : resolvedIPv4Address = YES; break; -- case AF_INET6 : resolvedIPv6Address = YES; break; -- -- default : NSAssert(NO, @"Addresses array contains invalid address"); -- } -- } -- -- BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; -- BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; -- -- if (isIPv4Disabled && !resolvedIPv6Address) -- { -- NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es)."; -- resultError = [self otherError:msg]; -- -- if (addressPtr) *addressPtr = resultAddress; -- if (errorPtr) *errorPtr = resultError; -- -- return resultAF; -- } -- -- if (isIPv6Disabled && !resolvedIPv4Address) -- { -- NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es)."; -- resultError = [self otherError:msg]; -- -- if (addressPtr) *addressPtr = resultAddress; -- if (errorPtr) *errorPtr = resultError; -- -- return resultAF; -- } -- -- BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO; -- BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO; -- -- if (isIPv4Deactivated && !resolvedIPv6Address) -- { -- NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es)."; -- resultError = [self otherError:msg]; -- -- if (addressPtr) *addressPtr = resultAddress; -- if (errorPtr) *errorPtr = resultError; -- -- return resultAF; -- } -- -- if (isIPv6Deactivated && !resolvedIPv4Address) -- { -- NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es)."; -- resultError = [self otherError:msg]; -- -- if (addressPtr) *addressPtr = resultAddress; -- if (errorPtr) *errorPtr = resultError; -- -- return resultAF; -- } -- -- // Extract first IPv4 and IPv6 address in list -- -- BOOL ipv4WasFirstInList = YES; -- NSData *address4 = nil; -- NSData *address6 = nil; -- -- for (NSData *address in addresses) -- { -- int af = [[self class] familyFromAddress:address]; -- -- if (af == AF_INET) -- { -- if (address4 == nil) -- { -- address4 = address; -- -- if (address6) -- break; -- else -- ipv4WasFirstInList = YES; -- } -- } -- else // af == AF_INET6 -- { -- if (address6 == nil) -- { -- address6 = address; -- -- if (address4) -- break; -- else -- ipv4WasFirstInList = NO; -- } -- } -- } -- -- // Determine socket type -- -- BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO; -- BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO; -- -- BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil)); -- BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil)); -- -- NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state"); -- NSAssert(!(useIPv4 && useIPv6), @"Invalid logic"); -- -- if (useIPv4 || (!useIPv6 && ipv4WasFirstInList)) -- { -- resultAF = AF_INET; -- resultAddress = address4; -- } -- else -- { -- resultAF = AF_INET6; -- resultAddress = address6; -- } -- -- if (addressPtr) *addressPtr = resultAddress; -- if (errorPtr) *errorPtr = resultError; -- -- return resultAF; --} -- --/** -- * Finds the address(es) of an interface description. -- * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34). --**/ --- (void)convertIntefaceDescription:(NSString *)interfaceDescription -- port:(uint16_t)port -- intoAddress4:(NSData **)interfaceAddr4Ptr -- address6:(NSData **)interfaceAddr6Ptr --{ -- NSData *addr4 = nil; -- NSData *addr6 = nil; -- -- if (interfaceDescription == nil) -- { -- // ANY address -- -- struct sockaddr_in sockaddr4; -- memset(&sockaddr4, 0, sizeof(sockaddr4)); -- -- sockaddr4.sin_len = sizeof(sockaddr4); -- sockaddr4.sin_family = AF_INET; -- sockaddr4.sin_port = htons(port); -- sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY); -- -- struct sockaddr_in6 sockaddr6; -- memset(&sockaddr6, 0, sizeof(sockaddr6)); -- -- sockaddr6.sin6_len = sizeof(sockaddr6); -- sockaddr6.sin6_family = AF_INET6; -- sockaddr6.sin6_port = htons(port); -- sockaddr6.sin6_addr = in6addr_any; -- -- addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; -- addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; -- } -- else if ([interfaceDescription isEqualToString:@"localhost"] || -- [interfaceDescription isEqualToString:@"loopback"]) -- { -- // LOOPBACK address -- -- struct sockaddr_in sockaddr4; -- memset(&sockaddr4, 0, sizeof(sockaddr4)); -- -- sockaddr4.sin_len = sizeof(struct sockaddr_in); -- sockaddr4.sin_family = AF_INET; -- sockaddr4.sin_port = htons(port); -- sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); -- -- struct sockaddr_in6 sockaddr6; -- memset(&sockaddr6, 0, sizeof(sockaddr6)); -- -- sockaddr6.sin6_len = sizeof(struct sockaddr_in6); -- sockaddr6.sin6_family = AF_INET6; -- sockaddr6.sin6_port = htons(port); -- sockaddr6.sin6_addr = in6addr_loopback; -- -- addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]; -- addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]; -- } -- else -- { -- const char *iface = [interfaceDescription UTF8String]; -- -- struct ifaddrs *addrs; -- const struct ifaddrs *cursor; -- -- if ((getifaddrs(&addrs) == 0)) -- { -- cursor = addrs; -- while (cursor != NULL) -- { -- if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET)) -- { -- // IPv4 -- -- struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; -- -- if (strcmp(cursor->ifa_name, iface) == 0) -- { -- // Name match -- -- struct sockaddr_in nativeAddr4 = *addr; -- nativeAddr4.sin_port = htons(port); -- -- addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; -- } -- else -- { -- char ip[INET_ADDRSTRLEN]; -- -- const char *conversion; -- conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip)); -- -- if ((conversion != NULL) && (strcmp(ip, iface) == 0)) -- { -- // IP match -- -- struct sockaddr_in nativeAddr4 = *addr; -- nativeAddr4.sin_port = htons(port); -- -- addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; -- } -- } -- } -- else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6)) -- { -- // IPv6 -- -- struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; -- -- if (strcmp(cursor->ifa_name, iface) == 0) -- { -- // Name match -- -- struct sockaddr_in6 nativeAddr6 = *addr; -- nativeAddr6.sin6_port = htons(port); -- -- addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; -- } -- else -- { -- char ip[INET6_ADDRSTRLEN]; -- -- const char *conversion; -- conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip)); -- -- if ((conversion != NULL) && (strcmp(ip, iface) == 0)) -- { -- // IP match -- -- struct sockaddr_in6 nativeAddr6 = *addr; -- nativeAddr6.sin6_port = htons(port); -- -- addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; -- } -- } -- } -- -- cursor = cursor->ifa_next; -- } -- -- freeifaddrs(addrs); -- } -- } -- -- if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4; -- if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6; --} -- --/** -- * Converts a numeric hostname into its corresponding address. -- * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34) --**/ --- (void)convertNumericHost:(NSString *)numericHost -- port:(uint16_t)port -- intoAddress4:(NSData **)addr4Ptr -- address6:(NSData **)addr6Ptr --{ -- NSData *addr4 = nil; -- NSData *addr6 = nil; -- -- if (numericHost) -- { -- NSString *portStr = [NSString stringWithFormat:@"%hu", port]; -- -- struct addrinfo hints, *res, *res0; -- -- memset(&hints, 0, sizeof(hints)); -- hints.ai_family = PF_UNSPEC; -- hints.ai_socktype = SOCK_DGRAM; -- hints.ai_protocol = IPPROTO_UDP; -- hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted -- -- if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0) -- { -- for (res = res0; res; res = res->ai_next) -- { -- if ((addr4 == nil) && (res->ai_family == AF_INET)) -- { -- // Found IPv4 address -- // Wrap the native address structure -- addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; -- } -- else if ((addr6 == nil) && (res->ai_family == AF_INET6)) -- { -- // Found IPv6 address -- // Wrap the native address structure -- addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; -- } -- } -- freeaddrinfo(res0); -- } -- } -- -- if (addr4Ptr) *addr4Ptr = addr4; -- if (addr6Ptr) *addr6Ptr = addr6; --} -- --- (BOOL)isConnectedToAddress4:(NSData *)someAddr4 --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(flags & kDidConnect, @"Not connected"); -- NSAssert(cachedConnectedAddress, @"Expected cached connected address"); -- -- if (cachedConnectedFamily != AF_INET) -- { -- return NO; -- } -- -- const struct sockaddr_in *sSockaddr4 = (struct sockaddr_in *)[someAddr4 bytes]; -- const struct sockaddr_in *cSockaddr4 = (struct sockaddr_in *)[cachedConnectedAddress bytes]; -- -- if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0) -- { -- return NO; -- } -- if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0) -- { -- return NO; -- } -- -- return YES; --} -- --- (BOOL)isConnectedToAddress6:(NSData *)someAddr6 --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(flags & kDidConnect, @"Not connected"); -- NSAssert(cachedConnectedAddress, @"Expected cached connected address"); -- -- if (cachedConnectedFamily != AF_INET6) -- { -- return NO; -- } -- -- const struct sockaddr_in6 *sSockaddr6 = (struct sockaddr_in6 *)[someAddr6 bytes]; -- const struct sockaddr_in6 *cSockaddr6 = (struct sockaddr_in6 *)[cachedConnectedAddress bytes]; -- -- if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0) -- { -- return NO; -- } -- if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0) -- { -- return NO; -- } -- -- return YES; --} -- --- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4 --{ -- if (interfaceAddr4 == nil) -- return 0; -- if ([interfaceAddr4 length] != sizeof(struct sockaddr_in)) -- return 0; -- -- int result = 0; -- struct sockaddr_in *ifaceAddr = (struct sockaddr_in *)[interfaceAddr4 bytes]; -- -- struct ifaddrs *addrs; -- const struct ifaddrs *cursor; -- -- if ((getifaddrs(&addrs) == 0)) -- { -- cursor = addrs; -- while (cursor != NULL) -- { -- if (cursor->ifa_addr->sa_family == AF_INET) -- { -- // IPv4 -- -- struct sockaddr_in *addr = (struct sockaddr_in *)cursor->ifa_addr; -- -- if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0) -- { -- result = if_nametoindex(cursor->ifa_name); -- break; -- } -- } -- -- cursor = cursor->ifa_next; -- } -- -- freeifaddrs(addrs); -- } -- -- return result; --} -- --- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6 --{ -- if (interfaceAddr6 == nil) -- return 0; -- if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6)) -- return 0; -- -- int result = 0; -- struct sockaddr_in6 *ifaceAddr = (struct sockaddr_in6 *)[interfaceAddr6 bytes]; -- -- struct ifaddrs *addrs; -- const struct ifaddrs *cursor; -- -- if ((getifaddrs(&addrs) == 0)) -- { -- cursor = addrs; -- while (cursor != NULL) -- { -- if (cursor->ifa_addr->sa_family == AF_INET6) -- { -- // IPv6 -- -- struct sockaddr_in6 *addr = (struct sockaddr_in6 *)cursor->ifa_addr; -- -- if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0) -- { -- result = if_nametoindex(cursor->ifa_name); -- break; -- } -- } -- -- cursor = cursor->ifa_next; -- } -- -- freeifaddrs(addrs); -- } -- -- return result; --} -- --- (void)setupSendAndReceiveSourcesForSocket4 --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue); -- receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue); -- -- // Setup event handlers -- -- dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool { -- -- LogVerbose(@"send4EventBlock"); -- LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source)); -- -- flags |= kSock4CanAcceptBytes; -- -- // If we're ready to send data, do so immediately. -- // Otherwise pause the send source or it will continue to fire over and over again. -- -- if (currentSend == nil) -- { -- LogVerbose(@"Nothing to send"); -- [self suspendSend4Source]; -- } -- else if (currentSend->resolveInProgress) -- { -- LogVerbose(@"currentSend - waiting for address resolve"); -- [self suspendSend4Source]; -- } -- else if (currentSend->filterInProgress) -- { -- LogVerbose(@"currentSend - waiting on sendFilter"); -- [self suspendSend4Source]; -- } -- else -- { -- [self doSend]; -- } -- -- }}); -- -- dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool { -- -- LogVerbose(@"receive4EventBlock"); -- -- socket4FDBytesAvailable = dispatch_source_get_data(receive4Source); -- LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable); -- -- if (socket4FDBytesAvailable > 0) -- [self doReceive]; -- else -- [self doReceiveEOF]; -- -- }}); -- -- // Setup cancel handlers -- -- __block int socketFDRefCount = 2; -- -- int theSocketFD = socket4FD; -- -- #if !OS_OBJECT_USE_OBJC -- dispatch_source_t theSendSource = send4Source; -- dispatch_source_t theReceiveSource = receive4Source; -- #endif -- -- dispatch_source_set_cancel_handler(send4Source, ^{ -- -- LogVerbose(@"send4CancelBlock"); -- -- #if !OS_OBJECT_USE_OBJC -- LogVerbose(@"dispatch_release(send4Source)"); -- dispatch_release(theSendSource); -- #endif -- -- if (--socketFDRefCount == 0) -- { -- LogVerbose(@"close(socket4FD)"); -- close(theSocketFD); -- } -- }); -- -- dispatch_source_set_cancel_handler(receive4Source, ^{ -- -- LogVerbose(@"receive4CancelBlock"); -- -- #if !OS_OBJECT_USE_OBJC -- LogVerbose(@"dispatch_release(receive4Source)"); -- dispatch_release(theReceiveSource); -- #endif -- -- if (--socketFDRefCount == 0) -- { -- LogVerbose(@"close(socket4FD)"); -- close(theSocketFD); -- } -- }); -- -- // We will not be able to receive until the socket is bound to a port, -- // either explicitly via bind, or implicitly by connect or by sending data. -- // -- // But we should be able to send immediately. -- -- socket4FDBytesAvailable = 0; -- flags |= kSock4CanAcceptBytes; -- -- flags |= kSend4SourceSuspended; -- flags |= kReceive4SourceSuspended; --} -- --- (void)setupSendAndReceiveSourcesForSocket6 --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue); -- receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue); -- -- // Setup event handlers -- -- dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool { -- -- LogVerbose(@"send6EventBlock"); -- LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source)); -- -- flags |= kSock6CanAcceptBytes; -- -- // If we're ready to send data, do so immediately. -- // Otherwise pause the send source or it will continue to fire over and over again. -- -- if (currentSend == nil) -- { -- LogVerbose(@"Nothing to send"); -- [self suspendSend6Source]; -- } -- else if (currentSend->resolveInProgress) -- { -- LogVerbose(@"currentSend - waiting for address resolve"); -- [self suspendSend6Source]; -- } -- else if (currentSend->filterInProgress) -- { -- LogVerbose(@"currentSend - waiting on sendFilter"); -- [self suspendSend6Source]; -- } -- else -- { -- [self doSend]; -- } -- -- }}); -- -- dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool { -- -- LogVerbose(@"receive6EventBlock"); -- -- socket6FDBytesAvailable = dispatch_source_get_data(receive6Source); -- LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable); -- -- if (socket6FDBytesAvailable > 0) -- [self doReceive]; -- else -- [self doReceiveEOF]; -- -- }}); -- -- // Setup cancel handlers -- -- __block int socketFDRefCount = 2; -- -- int theSocketFD = socket6FD; -- -- #if !OS_OBJECT_USE_OBJC -- dispatch_source_t theSendSource = send6Source; -- dispatch_source_t theReceiveSource = receive6Source; -- #endif -- -- dispatch_source_set_cancel_handler(send6Source, ^{ -- -- LogVerbose(@"send6CancelBlock"); -- -- #if !OS_OBJECT_USE_OBJC -- LogVerbose(@"dispatch_release(send6Source)"); -- dispatch_release(theSendSource); -- #endif -- -- if (--socketFDRefCount == 0) -- { -- LogVerbose(@"close(socket6FD)"); -- close(theSocketFD); -- } -- }); -- -- dispatch_source_set_cancel_handler(receive6Source, ^{ -- -- LogVerbose(@"receive6CancelBlock"); -- -- #if !OS_OBJECT_USE_OBJC -- LogVerbose(@"dispatch_release(receive6Source)"); -- dispatch_release(theReceiveSource); -- #endif -- -- if (--socketFDRefCount == 0) -- { -- LogVerbose(@"close(socket6FD)"); -- close(theSocketFD); -- } -- }); -- -- // We will not be able to receive until the socket is bound to a port, -- // either explicitly via bind, or implicitly by connect or by sending data. -- // -- // But we should be able to send immediately. -- -- socket6FDBytesAvailable = 0; -- flags |= kSock6CanAcceptBytes; -- -- flags |= kSend6SourceSuspended; -- flags |= kReceive6SourceSuspended; --} -- --- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError **)errPtr --{ -- LogTrace(); -- -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created"); -- -- // CreateSocket Block -- // This block will be invoked below. -- -- int(^createSocket)(int) = ^int (int domain) { -- -- int socketFD = socket(domain, SOCK_DGRAM, 0); -- -- if (socketFD == SOCKET_NULL) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error in socket() function"]; -- -- return SOCKET_NULL; -- } -- -- int status; -- -- // Set socket options -- -- status = fcntl(socketFD, F_SETFL, O_NONBLOCK); -- if (status == -1) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"]; -- -- close(socketFD); -- return SOCKET_NULL; -- } -- -- int reuseaddr = 1; -- status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)); -- if (status == -1) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"]; -- -- close(socketFD); -- return SOCKET_NULL; -- } -- -- int nosigpipe = 1; -- status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe)); -- if (status == -1) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"]; -- -- close(socketFD); -- return SOCKET_NULL; -- } -- -- /** -- * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. -- * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. -- * -- * The default maximum size of the UDP buffer in iOS is 9216 bytes. -- * -- * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and -- * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K) -- * -- * -- * Enlarge the maximum size of UDP packet. -- * I can not ensure the protocol type now so that the max size is set to 65535 :) -- **/ -- -- status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&maxSendSize, sizeof(int)); -- if (status == -1) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"]; -- close(socketFD); -- return SOCKET_NULL; -- } -- -- status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&maxSendSize, sizeof(int)); -- if (status == -1) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"]; -- close(socketFD); -- return SOCKET_NULL; -- } -- -- -- return socketFD; -- }; -- -- // Create sockets depending upon given configuration. -- -- if (useIPv4) -- { -- LogVerbose(@"Creating IPv4 socket"); -- -- socket4FD = createSocket(AF_INET); -- if (socket4FD == SOCKET_NULL) -- { -- // errPtr set in local createSocket() block -- return NO; -- } -- } -- -- if (useIPv6) -- { -- LogVerbose(@"Creating IPv6 socket"); -- -- socket6FD = createSocket(AF_INET6); -- if (socket6FD == SOCKET_NULL) -- { -- // errPtr set in local createSocket() block -- -- if (socket4FD != SOCKET_NULL) -- { -- close(socket4FD); -- socket4FD = SOCKET_NULL; -- } -- -- return NO; -- } -- } -- -- // Setup send and receive sources -- -- if (useIPv4) -- [self setupSendAndReceiveSourcesForSocket4]; -- if (useIPv6) -- [self setupSendAndReceiveSourcesForSocket6]; -- -- flags |= kDidCreateSockets; -- return YES; --} -- --- (BOOL)createSockets:(NSError **)errPtr --{ -- LogTrace(); -- -- BOOL useIPv4 = [self isIPv4Enabled]; -- BOOL useIPv6 = [self isIPv6Enabled]; -- -- return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr]; --} -- --- (void)suspendSend4Source --{ -- if (send4Source && !(flags & kSend4SourceSuspended)) -- { -- LogVerbose(@"dispatch_suspend(send4Source)"); -- -- dispatch_suspend(send4Source); -- flags |= kSend4SourceSuspended; -- } --} -- --- (void)suspendSend6Source --{ -- if (send6Source && !(flags & kSend6SourceSuspended)) -- { -- LogVerbose(@"dispatch_suspend(send6Source)"); -- -- dispatch_suspend(send6Source); -- flags |= kSend6SourceSuspended; -- } --} -- --- (void)resumeSend4Source --{ -- if (send4Source && (flags & kSend4SourceSuspended)) -- { -- LogVerbose(@"dispatch_resume(send4Source)"); -- -- dispatch_resume(send4Source); -- flags &= ~kSend4SourceSuspended; -- } --} -- --- (void)resumeSend6Source --{ -- if (send6Source && (flags & kSend6SourceSuspended)) -- { -- LogVerbose(@"dispatch_resume(send6Source)"); -- -- dispatch_resume(send6Source); -- flags &= ~kSend6SourceSuspended; -- } --} -- --- (void)suspendReceive4Source --{ -- if (receive4Source && !(flags & kReceive4SourceSuspended)) -- { -- LogVerbose(@"dispatch_suspend(receive4Source)"); -- -- dispatch_suspend(receive4Source); -- flags |= kReceive4SourceSuspended; -- } --} -- --- (void)suspendReceive6Source --{ -- if (receive6Source && !(flags & kReceive6SourceSuspended)) -- { -- LogVerbose(@"dispatch_suspend(receive6Source)"); -- -- dispatch_suspend(receive6Source); -- flags |= kReceive6SourceSuspended; -- } --} -- --- (void)resumeReceive4Source --{ -- if (receive4Source && (flags & kReceive4SourceSuspended)) -- { -- LogVerbose(@"dispatch_resume(receive4Source)"); -- -- dispatch_resume(receive4Source); -- flags &= ~kReceive4SourceSuspended; -- } --} -- --- (void)resumeReceive6Source --{ -- if (receive6Source && (flags & kReceive6SourceSuspended)) -- { -- LogVerbose(@"dispatch_resume(receive6Source)"); -- -- dispatch_resume(receive6Source); -- flags &= ~kReceive6SourceSuspended; -- } --} -- --- (void)closeSocket4 --{ -- if (socket4FD != SOCKET_NULL) -- { -- LogVerbose(@"dispatch_source_cancel(send4Source)"); -- dispatch_source_cancel(send4Source); -- -- LogVerbose(@"dispatch_source_cancel(receive4Source)"); -- dispatch_source_cancel(receive4Source); -- -- // For some crazy reason (in my opinion), cancelling a dispatch source doesn't -- // invoke the cancel handler if the dispatch source is paused. -- // So we have to unpause the source if needed. -- // This allows the cancel handler to be run, which in turn releases the source and closes the socket. -- -- [self resumeSend4Source]; -- [self resumeReceive4Source]; -- -- // The sockets will be closed by the cancel handlers of the corresponding source -- -- send4Source = NULL; -- receive4Source = NULL; -- -- socket4FD = SOCKET_NULL; -- -- // Clear socket states -- -- socket4FDBytesAvailable = 0; -- flags &= ~kSock4CanAcceptBytes; -- -- // Clear cached info -- -- cachedLocalAddress4 = nil; -- cachedLocalHost4 = nil; -- cachedLocalPort4 = 0; -- } --} -- --- (void)closeSocket6 --{ -- if (socket6FD != SOCKET_NULL) -- { -- LogVerbose(@"dispatch_source_cancel(send6Source)"); -- dispatch_source_cancel(send6Source); -- -- LogVerbose(@"dispatch_source_cancel(receive6Source)"); -- dispatch_source_cancel(receive6Source); -- -- // For some crazy reason (in my opinion), cancelling a dispatch source doesn't -- // invoke the cancel handler if the dispatch source is paused. -- // So we have to unpause the source if needed. -- // This allows the cancel handler to be run, which in turn releases the source and closes the socket. -- -- [self resumeSend6Source]; -- [self resumeReceive6Source]; -- -- send6Source = NULL; -- receive6Source = NULL; -- -- // The sockets will be closed by the cancel handlers of the corresponding source -- -- socket6FD = SOCKET_NULL; -- -- // Clear socket states -- -- socket6FDBytesAvailable = 0; -- flags &= ~kSock6CanAcceptBytes; -- -- // Clear cached info -- -- cachedLocalAddress6 = nil; -- cachedLocalHost6 = nil; -- cachedLocalPort6 = 0; -- } --} -- --- (void)closeSockets --{ -- [self closeSocket4]; -- [self closeSocket6]; -- -- flags &= ~kDidCreateSockets; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Diagnostics --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)getLocalAddress:(NSData **)dataPtr -- host:(NSString **)hostPtr -- port:(uint16_t *)portPtr -- forSocket:(int)socketFD -- withFamily:(int)socketFamily --{ -- -- NSData *data = nil; -- NSString *host = nil; -- uint16_t port = 0; -- -- if (socketFamily == AF_INET) -- { -- struct sockaddr_in sockaddr4; -- socklen_t sockaddr4len = sizeof(sockaddr4); -- -- if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) -- { -- data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; -- host = [[self class] hostFromSockaddr4:&sockaddr4]; -- port = [[self class] portFromSockaddr4:&sockaddr4]; -- } -- else -- { -- LogWarn(@"Error in getsockname: %@", [self errnoError]); -- } -- } -- else if (socketFamily == AF_INET6) -- { -- struct sockaddr_in6 sockaddr6; -- socklen_t sockaddr6len = sizeof(sockaddr6); -- -- if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) -- { -- data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; -- host = [[self class] hostFromSockaddr6:&sockaddr6]; -- port = [[self class] portFromSockaddr6:&sockaddr6]; -- } -- else -- { -- LogWarn(@"Error in getsockname: %@", [self errnoError]); -- } -- } -- -- if (dataPtr) *dataPtr = data; -- if (hostPtr) *hostPtr = host; -- if (portPtr) *portPtr = port; -- -- return (data != nil); --} -- --- (void)maybeUpdateCachedLocalAddress4Info --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) ) -- { -- return; -- } -- -- NSData *address = nil; -- NSString *host = nil; -- uint16_t port = 0; -- -- if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET]) -- { -- -- cachedLocalAddress4 = address; -- cachedLocalHost4 = host; -- cachedLocalPort4 = port; -- } --} -- --- (void)maybeUpdateCachedLocalAddress6Info --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) ) -- { -- return; -- } -- -- NSData *address = nil; -- NSString *host = nil; -- uint16_t port = 0; -- -- if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6]) -- { -- -- cachedLocalAddress6 = address; -- cachedLocalHost6 = host; -- cachedLocalPort6 = port; -- } --} -- --- (NSData *)localAddress --{ -- __block NSData *result = nil; -- -- dispatch_block_t block = ^{ -- -- if (socket4FD != SOCKET_NULL) -- { -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalAddress4; -- } -- else -- { -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalAddress6; -- } -- -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSString *)localHost --{ -- __block NSString *result = nil; -- -- dispatch_block_t block = ^{ -- -- if (socket4FD != SOCKET_NULL) -- { -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalHost4; -- } -- else -- { -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalHost6; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (uint16_t)localPort --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- if (socket4FD != SOCKET_NULL) -- { -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalPort4; -- } -- else -- { -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalPort6; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSData *)localAddress_IPv4 --{ -- __block NSData *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalAddress4; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSString *)localHost_IPv4 --{ -- __block NSString *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalHost4; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (uint16_t)localPort_IPv4 --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress4Info]; -- result = cachedLocalPort4; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSData *)localAddress_IPv6 --{ -- __block NSData *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalAddress6; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSString *)localHost_IPv6 --{ -- __block NSString *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalHost6; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (uint16_t)localPort_IPv6 --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedLocalAddress6Info]; -- result = cachedLocalPort6; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (void)maybeUpdateCachedConnectedAddressInfo --{ -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if (cachedConnectedAddress || (flags & kDidConnect) == 0) -- { -- return; -- } -- -- NSData *data = nil; -- NSString *host = nil; -- uint16_t port = 0; -- int family = AF_UNSPEC; -- -- if (socket4FD != SOCKET_NULL) -- { -- struct sockaddr_in sockaddr4; -- socklen_t sockaddr4len = sizeof(sockaddr4); -- -- if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0) -- { -- data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; -- host = [[self class] hostFromSockaddr4:&sockaddr4]; -- port = [[self class] portFromSockaddr4:&sockaddr4]; -- family = AF_INET; -- } -- else -- { -- LogWarn(@"Error in getpeername: %@", [self errnoError]); -- } -- } -- else if (socket6FD != SOCKET_NULL) -- { -- struct sockaddr_in6 sockaddr6; -- socklen_t sockaddr6len = sizeof(sockaddr6); -- -- if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0) -- { -- data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; -- host = [[self class] hostFromSockaddr6:&sockaddr6]; -- port = [[self class] portFromSockaddr6:&sockaddr6]; -- family = AF_INET6; -- } -- else -- { -- LogWarn(@"Error in getpeername: %@", [self errnoError]); -- } -- } -- -- -- cachedConnectedAddress = data; -- cachedConnectedHost = host; -- cachedConnectedPort = port; -- cachedConnectedFamily = family; --} -- --- (NSData *)connectedAddress --{ -- __block NSData *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedConnectedAddressInfo]; -- result = cachedConnectedAddress; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (NSString *)connectedHost --{ -- __block NSString *result = nil; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedConnectedAddressInfo]; -- result = cachedConnectedHost; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (uint16_t)connectedPort --{ -- __block uint16_t result = 0; -- -- dispatch_block_t block = ^{ -- -- [self maybeUpdateCachedConnectedAddressInfo]; -- result = cachedConnectedPort; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, AutoreleasedBlock(block)); -- -- return result; --} -- --- (BOOL)isConnected --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- result = (flags & kDidConnect) ? YES : NO; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (BOOL)isClosed --{ -- __block BOOL result = YES; -- -- dispatch_block_t block = ^{ -- -- result = (flags & kDidCreateSockets) ? NO : YES; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (BOOL)isIPv4 --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- -- if (flags & kDidCreateSockets) -- { -- result = (socket4FD != SOCKET_NULL); -- } -- else -- { -- result = [self isIPv4Enabled]; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --- (BOOL)isIPv6 --{ -- __block BOOL result = NO; -- -- dispatch_block_t block = ^{ -- -- if (flags & kDidCreateSockets) -- { -- result = (socket6FD != SOCKET_NULL); -- } -- else -- { -- result = [self isIPv6Enabled]; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- return result; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Binding --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --/** -- * This method runs through the various checks required prior to a bind attempt. -- * It is shared between the various bind methods. --**/ --- (BOOL)preBind:(NSError **)errPtr --{ -- if (![self preOp:errPtr]) -- { -- return NO; -- } -- -- if (flags & kDidBind) -- { -- if (errPtr) -- { -- NSString *msg = @"Cannot bind a socket more than once."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- if ((flags & kConnecting) || (flags & kDidConnect)) -- { -- if (errPtr) -- { -- NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; -- BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; -- -- if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled -- { -- if (errPtr) -- { -- NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- return YES; --} -- --- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr --{ -- return [self bindToPort:port interface:nil error:errPtr]; --} -- --- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- // Run through sanity checks -- -- if (![self preBind:&err]) -- { -- return_from_block; -- } -- -- // Check the given interface -- -- NSData *interface4 = nil; -- NSData *interface6 = nil; -- -- [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6]; -- -- if ((interface4 == nil) && (interface6 == nil)) -- { -- NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; -- BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; -- -- if (isIPv4Disabled && (interface6 == nil)) -- { -- NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- if (isIPv6Disabled && (interface4 == nil)) -- { -- NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Determine protocol(s) -- -- BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil); -- BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil); -- -- // Create the socket(s) if needed -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) -- { -- return_from_block; -- } -- } -- -- // Bind the socket(s) -- -- LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface); -- -- if (useIPv4) -- { -- int status = bind(socket4FD, (struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]); -- if (status == -1) -- { -- [self closeSockets]; -- -- NSString *reason = @"Error in bind() function"; -- err = [self errnoErrorWithReason:reason]; -- -- return_from_block; -- } -- } -- -- if (useIPv6) -- { -- int status = bind(socket6FD, (struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]); -- if (status == -1) -- { -- [self closeSockets]; -- -- NSString *reason = @"Error in bind() function"; -- err = [self errnoErrorWithReason:reason]; -- -- return_from_block; -- } -- } -- -- // Update flags -- -- flags |= kDidBind; -- -- if (!useIPv4) flags |= kIPv4Deactivated; -- if (!useIPv6) flags |= kIPv6Deactivated; -- -- result = YES; -- -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error binding to port/interface: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- // Run through sanity checks -- -- if (![self preBind:&err]) -- { -- return_from_block; -- } -- -- // Check the given address -- -- int addressFamily = [[self class] familyFromAddress:localAddr]; -- -- if (addressFamily == AF_UNSPEC) -- { -- NSString *msg = @"A valid IPv4 or IPv6 address was not given"; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil; -- NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil; -- -- BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; -- BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; -- -- if (isIPv4Disabled && localAddr4) -- { -- NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- if (isIPv6Disabled && localAddr6) -- { -- NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Determine protocol(s) -- -- BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil); -- BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil); -- -- // Create the socket(s) if needed -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err]) -- { -- return_from_block; -- } -- } -- -- // Bind the socket(s) -- -- if (useIPv4) -- { -- LogVerbose(@"Binding socket to address(%@:%hu)", -- [[self class] hostFromAddress:localAddr4], -- [[self class] portFromAddress:localAddr4]); -- -- int status = bind(socket4FD, (struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]); -- if (status == -1) -- { -- [self closeSockets]; -- -- NSString *reason = @"Error in bind() function"; -- err = [self errnoErrorWithReason:reason]; -- -- return_from_block; -- } -- } -- else -- { -- LogVerbose(@"Binding socket to address(%@:%hu)", -- [[self class] hostFromAddress:localAddr6], -- [[self class] portFromAddress:localAddr6]); -- -- int status = bind(socket6FD, (struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]); -- if (status == -1) -- { -- [self closeSockets]; -- -- NSString *reason = @"Error in bind() function"; -- err = [self errnoErrorWithReason:reason]; -- -- return_from_block; -- } -- } -- -- // Update flags -- -- flags |= kDidBind; -- -- if (!useIPv4) flags |= kIPv4Deactivated; -- if (!useIPv6) flags |= kIPv6Deactivated; -- -- result = YES; -- -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error binding to address: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Connecting --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --/** -- * This method runs through the various checks required prior to a connect attempt. -- * It is shared between the various connect methods. --**/ --- (BOOL)preConnect:(NSError **)errPtr --{ -- if (![self preOp:errPtr]) -- { -- return NO; -- } -- -- if ((flags & kConnecting) || (flags & kDidConnect)) -- { -- if (errPtr) -- { -- NSString *msg = @"Cannot connect a socket more than once."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO; -- BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO; -- -- if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled -- { -- if (errPtr) -- { -- NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- return YES; --} -- --- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- // Run through sanity checks. -- -- if (![self preConnect:&err]) -- { -- return_from_block; -- } -- -- // Check parameter(s) -- -- if (host == nil) -- { -- NSString *msg = @"The host param is nil. Should be domain name or IP address string."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Create the socket(s) if needed -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSockets:&err]) -- { -- return_from_block; -- } -- } -- -- // Create special connect packet -- -- GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; -- packet->resolveInProgress = YES; -- -- // Start asynchronous DNS resolve for host:port on background queue -- -- LogVerbose(@"Dispatching DNS resolve for connect..."); -- -- [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { -- -- // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, -- // and immediately returns. Once the async resolve task completes, -- // this block is executed on our socketQueue. -- -- packet->resolveInProgress = NO; -- -- packet->addresses = addresses; -- packet->error = error; -- -- [self maybeConnect]; -- }]; -- -- // Updates flags, add connect packet to send queue, and pump send queue -- -- flags |= kConnecting; -- -- [sendQueue addObject:packet]; -- [self maybeDequeueSend]; -- -- result = YES; -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error connecting to host/port: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- // Run through sanity checks. -- -- if (![self preConnect:&err]) -- { -- return_from_block; -- } -- -- // Check parameter(s) -- -- if (remoteAddr == nil) -- { -- NSString *msg = @"The address param is nil. Should be a valid address."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Create the socket(s) if needed -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSockets:&err]) -- { -- return_from_block; -- } -- } -- -- // The remoteAddr parameter could be of type NSMutableData. -- // So we copy it to be safe. -- -- NSData *address = [remoteAddr copy]; -- NSArray *addresses = [NSArray arrayWithObject:address]; -- -- GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init]; -- packet->addresses = addresses; -- -- // Updates flags, add connect packet to send queue, and pump send queue -- -- flags |= kConnecting; -- -- [sendQueue addObject:packet]; -- [self maybeDequeueSend]; -- -- result = YES; -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error connecting to address: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --- (void)maybeConnect --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- -- BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]; -- -- if (sendQueueReady) -- { -- GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend; -- -- if (connectPacket->resolveInProgress) -- { -- LogVerbose(@"Waiting for DNS resolve..."); -- } -- else -- { -- if (connectPacket->error) -- { -- [self notifyDidNotConnect:connectPacket->error]; -- } -- else -- { -- NSData *address = nil; -- NSError *error = nil; -- -- int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses]; -- -- // Perform connect -- -- BOOL result = NO; -- -- switch (addressFamily) -- { -- case AF_INET : result = [self connectWithAddress4:address error:&error]; break; -- case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break; -- } -- -- if (result) -- { -- flags |= kDidBind; -- flags |= kDidConnect; -- -- cachedConnectedAddress = address; -- cachedConnectedHost = [[self class] hostFromAddress:address]; -- cachedConnectedPort = [[self class] portFromAddress:address]; -- cachedConnectedFamily = addressFamily; -- -- [self notifyDidConnectToAddress:address]; -- } -- else -- { -- [self notifyDidNotConnect:error]; -- } -- } -- -- flags &= ~kConnecting; -- -- [self endCurrentSend]; -- [self maybeDequeueSend]; -- } -- } --} -- --- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- int status = connect(socket4FD, (struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]); -- if (status != 0) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; -- -- return NO; -- } -- -- [self closeSocket6]; -- flags |= kIPv6Deactivated; -- -- return YES; --} -- --- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- int status = connect(socket6FD, (struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]); -- if (status != 0) -- { -- if (errPtr) -- *errPtr = [self errnoErrorWithReason:@"Error in connect() function"]; -- -- return NO; -- } -- -- [self closeSocket4]; -- flags |= kIPv4Deactivated; -- -- return YES; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Multicast --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)preJoin:(NSError **)errPtr --{ -- if (![self preOp:errPtr]) -- { -- return NO; -- } -- -- if (!(flags & kDidBind)) -- { -- if (errPtr) -- { -- NSString *msg = @"Must bind a socket before joining a multicast group."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- if ((flags & kConnecting) || (flags & kDidConnect)) -- { -- if (errPtr) -- { -- NSString *msg = @"Cannot join a multicast group if connected."; -- *errPtr = [self badConfigError:msg]; -- } -- return NO; -- } -- -- return YES; --} -- --- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr --{ -- return [self joinMulticastGroup:group onInterface:nil error:errPtr]; --} -- --- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr --{ -- // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP -- return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; --} -- --- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr --{ -- return [self leaveMulticastGroup:group onInterface:nil error:errPtr]; --} -- --- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr --{ -- // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP -- return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr]; --} -- --- (BOOL)performMulticastRequest:(int)requestType -- forGroup:(NSString *)group -- onInterface:(NSString *)interface -- error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- // Run through sanity checks -- -- if (![self preJoin:&err]) -- { -- return_from_block; -- } -- -- // Convert group to address -- -- NSData *groupAddr4 = nil; -- NSData *groupAddr6 = nil; -- -- [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6]; -- -- if ((groupAddr4 == nil) && (groupAddr6 == nil)) -- { -- NSString *msg = @"Unknown group. Specify valid group IP address."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Convert interface to address -- -- NSData *interfaceAddr4 = nil; -- NSData *interfaceAddr6 = nil; -- -- [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6]; -- -- if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil)) -- { -- NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address."; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- // Perform join -- -- if ((socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4) -- { -- const struct sockaddr_in *nativeGroup = (struct sockaddr_in *)[groupAddr4 bytes]; -- const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes]; -- -- struct ip_mreq imreq; -- imreq.imr_multiaddr = nativeGroup->sin_addr; -- imreq.imr_interface = nativeIface->sin_addr; -- -- int status = setsockopt(socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq)); -- if (status != 0) -- { -- err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; -- -- return_from_block; -- } -- -- // Using IPv4 only -- [self closeSocket6]; -- -- result = YES; -- } -- else if ((socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6) -- { -- const struct sockaddr_in6 *nativeGroup = (struct sockaddr_in6 *)[groupAddr6 bytes]; -- -- struct ipv6_mreq imreq; -- imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr; -- imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6]; -- -- int status = setsockopt(socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq)); -- if (status != 0) -- { -- err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; -- -- return_from_block; -- } -- -- // Using IPv6 only -- [self closeSocket4]; -- -- result = YES; -- } -- else -- { -- NSString *msg = @"Socket, group, and interface do not have matching IP versions"; -- err = [self badParamError:msg]; -- -- return_from_block; -- } -- -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Reuse port --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- if (![self preOp:&err]) -- { -- return_from_block; -- } -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSockets:&err]) -- { -- return_from_block; -- } -- } -- -- int value = flag ? 1 : 0; -- if (socket4FD != SOCKET_NULL) -- { -- int error = setsockopt(socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); -- -- if (error) -- { -- err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; -- -- return_from_block; -- } -- result = YES; -- } -- -- if (socket6FD != SOCKET_NULL) -- { -- int error = setsockopt(socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value)); -- -- if (error) -- { -- err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; -- -- return_from_block; -- } -- result = YES; -- } -- -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Broadcast --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr --{ -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- if (![self preOp:&err]) -- { -- return_from_block; -- } -- -- if ((flags & kDidCreateSockets) == 0) -- { -- if (![self createSockets:&err]) -- { -- return_from_block; -- } -- } -- -- if (socket4FD != SOCKET_NULL) -- { -- int value = flag ? 1 : 0; -- int error = setsockopt(socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value)); -- -- if (error) -- { -- err = [self errnoErrorWithReason:@"Error in setsockopt() function"]; -- -- return_from_block; -- } -- result = YES; -- } -- -- // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link. -- // The same effect can be achieved by sending a packet to the link-local all hosts multicast group. -- -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Sending --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (void)sendData:(NSData *)data withTag:(long)tag --{ -- [self sendData:data withTimeout:-1.0 tag:tag]; --} -- --- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag --{ -- LogTrace(); -- -- if ([data length] == 0) -- { -- LogWarn(@"Ignoring attempt to send nil/empty data."); -- return; -- } -- -- -- -- GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- [sendQueue addObject:packet]; -- [self maybeDequeueSend]; -- }}); -- --} -- --- (void)sendData:(NSData *)data -- toHost:(NSString *)host -- port:(uint16_t)port -- withTimeout:(NSTimeInterval)timeout -- tag:(long)tag --{ -- LogTrace(); -- -- if ([data length] == 0) -- { -- LogWarn(@"Ignoring attempt to send nil/empty data."); -- return; -- } -- -- GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; -- packet->resolveInProgress = YES; -- -- [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) { -- -- // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue, -- // and immediately returns. Once the async resolve task completes, -- // this block is executed on our socketQueue. -- -- packet->resolveInProgress = NO; -- -- packet->resolvedAddresses = addresses; -- packet->resolveError = error; -- -- if (packet == currentSend) -- { -- LogVerbose(@"currentSend - address resolved"); -- [self doPreSend]; -- } -- }]; -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- [sendQueue addObject:packet]; -- [self maybeDequeueSend]; -- -- }}); -- --} -- --- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag --{ -- LogTrace(); -- -- if ([data length] == 0) -- { -- LogWarn(@"Ignoring attempt to send nil/empty data."); -- return; -- } -- -- GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag]; -- packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr]; -- packet->address = remoteAddr; -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- [sendQueue addObject:packet]; -- [self maybeDequeueSend]; -- }}); --} -- --- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue --{ -- [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; --} -- --- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock -- withQueue:(dispatch_queue_t)filterQueue -- isAsynchronous:(BOOL)isAsynchronous --{ -- GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL; -- dispatch_queue_t newFilterQueue = NULL; -- -- if (filterBlock) -- { -- NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); -- -- newFilterBlock = [filterBlock copy]; -- newFilterQueue = filterQueue; -- #if !OS_OBJECT_USE_OBJC -- dispatch_retain(newFilterQueue); -- #endif -- } -- -- dispatch_block_t block = ^{ -- -- #if !OS_OBJECT_USE_OBJC -- if (sendFilterQueue) dispatch_release(sendFilterQueue); -- #endif -- -- sendFilterBlock = newFilterBlock; -- sendFilterQueue = newFilterQueue; -- sendFilterAsync = isAsynchronous; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)maybeDequeueSend --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- // If we don't have a send operation already in progress -- if (currentSend == nil) -- { -- // Create the sockets if needed -- if ((flags & kDidCreateSockets) == 0) -- { -- NSError *err = nil; -- if (![self createSockets:&err]) -- { -- [self closeWithError:err]; -- return; -- } -- } -- -- while ([sendQueue count] > 0) -- { -- // Dequeue the next object in the queue -- currentSend = [sendQueue objectAtIndex:0]; -- [sendQueue removeObjectAtIndex:0]; -- -- if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]]) -- { -- [self maybeConnect]; -- -- return; // The maybeConnect method, if it connects, will invoke this method again -- } -- else if (currentSend->resolveError) -- { -- // Notify delegate -- [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError]; -- -- // Clear currentSend -- currentSend = nil; -- -- continue; -- } -- else -- { -- // Start preprocessing checks on the send packet -- [self doPreSend]; -- -- break; -- } -- } -- -- if ((currentSend == nil) && (flags & kCloseAfterSends)) -- { -- [self closeWithError:nil]; -- } -- } --} -- --/** -- * This method is called after a sendPacket has been dequeued. -- * It performs various preprocessing checks on the packet, -- * and queries the sendFilter (if set) to determine if the packet can be sent. -- * -- * If the packet passes all checks, it will be passed on to the doSend method. --**/ --- (void)doPreSend --{ -- LogTrace(); -- -- // -- // 1. Check for problems with send packet -- // -- -- BOOL waitingForResolve = NO; -- NSError *error = nil; -- -- if (flags & kDidConnect) -- { -- // Connected socket -- -- if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError) -- { -- NSString *msg = @"Cannot specify destination of packet for connected socket"; -- error = [self badConfigError:msg]; -- } -- else -- { -- currentSend->address = cachedConnectedAddress; -- currentSend->addressFamily = cachedConnectedFamily; -- } -- } -- else -- { -- // Non-Connected socket -- -- if (currentSend->resolveInProgress) -- { -- // We're waiting for the packet's destination to be resolved. -- waitingForResolve = YES; -- } -- else if (currentSend->resolveError) -- { -- error = currentSend->resolveError; -- } -- else if (currentSend->address == nil) -- { -- if (currentSend->resolvedAddresses == nil) -- { -- NSString *msg = @"You must specify destination of packet for a non-connected socket"; -- error = [self badConfigError:msg]; -- } -- else -- { -- // Pick the proper address to use (out of possibly several resolved addresses) -- -- NSData *address = nil; -- int addressFamily = AF_UNSPEC; -- -- addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses]; -- -- currentSend->address = address; -- currentSend->addressFamily = addressFamily; -- } -- } -- } -- -- if (waitingForResolve) -- { -- // We're waiting for the packet's destination to be resolved. -- -- LogVerbose(@"currentSend - waiting for address resolve"); -- -- if (flags & kSock4CanAcceptBytes) { -- [self suspendSend4Source]; -- } -- if (flags & kSock6CanAcceptBytes) { -- [self suspendSend6Source]; -- } -- -- return; -- } -- -- if (error) -- { -- // Unable to send packet due to some error. -- // Notify delegate and move on. -- -- [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error]; -- [self endCurrentSend]; -- [self maybeDequeueSend]; -- -- return; -- } -- -- // -- // 2. Query sendFilter (if applicable) -- // -- -- if (sendFilterBlock && sendFilterQueue) -- { -- // Query sendFilter -- -- if (sendFilterAsync) -- { -- // Scenario 1 of 3 - Need to asynchronously query sendFilter -- -- currentSend->filterInProgress = YES; -- GCDAsyncUdpSendPacket *sendPacket = currentSend; -- -- dispatch_async(sendFilterQueue, ^{ @autoreleasepool { -- -- BOOL allowed = sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag); -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- sendPacket->filterInProgress = NO; -- if (sendPacket == currentSend) -- { -- if (allowed) -- { -- [self doSend]; -- } -- else -- { -- LogVerbose(@"currentSend - silently dropped by sendFilter"); -- -- [self notifyDidSendDataWithTag:currentSend->tag]; -- [self endCurrentSend]; -- [self maybeDequeueSend]; -- } -- } -- }}); -- }}); -- } -- else -- { -- // Scenario 2 of 3 - Need to synchronously query sendFilter -- -- __block BOOL allowed = YES; -- -- dispatch_sync(sendFilterQueue, ^{ @autoreleasepool { -- -- allowed = sendFilterBlock(currentSend->buffer, currentSend->address, currentSend->tag); -- }}); -- -- if (allowed) -- { -- [self doSend]; -- } -- else -- { -- LogVerbose(@"currentSend - silently dropped by sendFilter"); -- -- [self notifyDidSendDataWithTag:currentSend->tag]; -- [self endCurrentSend]; -- [self maybeDequeueSend]; -- } -- } -- } -- else // if (!sendFilterBlock || !sendFilterQueue) -- { -- // Scenario 3 of 3 - No sendFilter. Just go straight into sending. -- -- [self doSend]; -- } --} -- --/** -- * This method performs the actual sending of data in the currentSend packet. -- * It should only be called if the --**/ --- (void)doSend --{ -- LogTrace(); -- -- NSAssert(currentSend != nil, @"Invalid logic"); -- -- // Perform the actual send -- -- ssize_t result = 0; -- -- if (flags & kDidConnect) -- { -- // Connected socket -- -- const void *buffer = [currentSend->buffer bytes]; -- size_t length = (size_t)[currentSend->buffer length]; -- -- if (currentSend->addressFamily == AF_INET) -- { -- result = send(socket4FD, buffer, length, 0); -- LogVerbose(@"send(socket4FD) = %d", result); -- } -- else -- { -- result = send(socket6FD, buffer, length, 0); -- LogVerbose(@"send(socket6FD) = %d", result); -- } -- } -- else -- { -- // Non-Connected socket -- -- const void *buffer = [currentSend->buffer bytes]; -- size_t length = (size_t)[currentSend->buffer length]; -- -- const void *dst = [currentSend->address bytes]; -- socklen_t dstSize = (socklen_t)[currentSend->address length]; -- -- if (currentSend->addressFamily == AF_INET) -- { -- result = sendto(socket4FD, buffer, length, 0, dst, dstSize); -- LogVerbose(@"sendto(socket4FD) = %d", result); -- } -- else -- { -- result = sendto(socket6FD, buffer, length, 0, dst, dstSize); -- LogVerbose(@"sendto(socket6FD) = %d", result); -- } -- } -- -- // If the socket wasn't bound before, it is now -- -- if ((flags & kDidBind) == 0) -- { -- flags |= kDidBind; -- } -- -- // Check the results. -- // -- // From the send() & sendto() manpage: -- // -- // Upon successful completion, the number of bytes which were sent is returned. -- // Otherwise, -1 is returned and the global variable errno is set to indicate the error. -- -- BOOL waitingForSocket = NO; -- NSError *socketError = nil; -- -- if (result == 0) -- { -- waitingForSocket = YES; -- } -- else if (result < 0) -- { -- if (errno == EAGAIN) -- waitingForSocket = YES; -- else -- socketError = [self errnoErrorWithReason:@"Error in send() function."]; -- } -- -- if (waitingForSocket) -- { -- // Not enough room in the underlying OS socket send buffer. -- // Wait for a notification of available space. -- -- LogVerbose(@"currentSend - waiting for socket"); -- -- if (!(flags & kSock4CanAcceptBytes)) { -- [self resumeSend4Source]; -- } -- if (!(flags & kSock6CanAcceptBytes)) { -- [self resumeSend6Source]; -- } -- -- if ((sendTimer == NULL) && (currentSend->timeout >= 0.0)) -- { -- // Unable to send packet right away. -- // Start timer to timeout the send operation. -- -- [self setupSendTimerWithTimeout:currentSend->timeout]; -- } -- } -- else if (socketError) -- { -- [self closeWithError:socketError]; -- } -- else // done -- { -- [self notifyDidSendDataWithTag:currentSend->tag]; -- [self endCurrentSend]; -- [self maybeDequeueSend]; -- } --} -- --/** -- * Releases all resources associated with the currentSend. --**/ --- (void)endCurrentSend --{ -- if (sendTimer) -- { -- dispatch_source_cancel(sendTimer); -- #if !OS_OBJECT_USE_OBJC -- dispatch_release(sendTimer); -- #endif -- sendTimer = NULL; -- } -- -- currentSend = nil; --} -- --/** -- * Performs the operations to timeout the current send operation, and move on. --**/ --- (void)doSendTimeout --{ -- LogTrace(); -- -- [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]]; -- [self endCurrentSend]; -- [self maybeDequeueSend]; --} -- --/** -- * Sets up a timer that fires to timeout the current send operation. -- * This method should only be called once per send packet. --**/ --- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout --{ -- NSAssert(sendTimer == NULL, @"Invalid logic"); -- NSAssert(timeout >= 0.0, @"Invalid logic"); -- -- LogTrace(); -- -- sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue); -- -- dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool { -- -- [self doSendTimeout]; -- }}); -- -- dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)); -- -- dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0); -- dispatch_resume(sendTimer); --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Receiving --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (BOOL)receiveOnce:(NSError **)errPtr --{ -- LogTrace(); -- -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ -- -- if ((flags & kReceiveOnce) == 0) -- { -- if ((flags & kDidCreateSockets) == 0) -- { -- NSString *msg = @"Must bind socket before you can receive data. " -- @"You can do this explicitly via bind, or implicitly via connect or by sending data."; -- -- err = [self badConfigError:msg]; -- return_from_block; -- } -- -- flags |= kReceiveOnce; // Enable -- flags &= ~kReceiveContinuous; // Disable -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- [self doReceive]; -- }}); -- } -- -- result = YES; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error in beginReceiving: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --- (BOOL)beginReceiving:(NSError **)errPtr --{ -- LogTrace(); -- -- __block BOOL result = NO; -- __block NSError *err = nil; -- -- dispatch_block_t block = ^{ -- -- if ((flags & kReceiveContinuous) == 0) -- { -- if ((flags & kDidCreateSockets) == 0) -- { -- NSString *msg = @"Must bind socket before you can receive data. " -- @"You can do this explicitly via bind, or implicitly via connect or by sending data."; -- -- err = [self badConfigError:msg]; -- return_from_block; -- } -- -- flags |= kReceiveContinuous; // Enable -- flags &= ~kReceiveOnce; // Disable -- -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- [self doReceive]; -- }}); -- } -- -- result = YES; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); -- -- if (err) -- LogError(@"Error in beginReceiving: %@", err); -- -- if (errPtr) -- *errPtr = err; -- -- return result; --} -- --- (void)pauseReceiving --{ -- LogTrace(); -- -- dispatch_block_t block = ^{ -- -- flags &= ~kReceiveOnce; // Disable -- flags &= ~kReceiveContinuous; // Disable -- -- if (socket4FDBytesAvailable > 0) { -- [self suspendReceive4Source]; -- } -- if (socket6FDBytesAvailable > 0) { -- [self suspendReceive6Source]; -- } -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue --{ -- [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES]; --} -- --- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock -- withQueue:(dispatch_queue_t)filterQueue -- isAsynchronous:(BOOL)isAsynchronous --{ -- GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL; -- dispatch_queue_t newFilterQueue = NULL; -- -- if (filterBlock) -- { -- NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block."); -- -- newFilterBlock = [filterBlock copy]; -- newFilterQueue = filterQueue; -- #if !OS_OBJECT_USE_OBJC -- dispatch_retain(newFilterQueue); -- #endif -- } -- -- dispatch_block_t block = ^{ -- -- #if !OS_OBJECT_USE_OBJC -- if (receiveFilterQueue) dispatch_release(receiveFilterQueue); -- #endif -- -- receiveFilterBlock = newFilterBlock; -- receiveFilterQueue = newFilterQueue; -- receiveFilterAsync = isAsynchronous; -- }; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --- (void)doReceive --{ -- LogTrace(); -- -- if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0) -- { -- LogVerbose(@"Receiving is paused..."); -- -- if (socket4FDBytesAvailable > 0) { -- [self suspendReceive4Source]; -- } -- if (socket6FDBytesAvailable > 0) { -- [self suspendReceive6Source]; -- } -- -- return; -- } -- -- if ((flags & kReceiveOnce) && (pendingFilterOperations > 0)) -- { -- LogVerbose(@"Receiving is temporarily paused (pending filter operations)..."); -- -- if (socket4FDBytesAvailable > 0) { -- [self suspendReceive4Source]; -- } -- if (socket6FDBytesAvailable > 0) { -- [self suspendReceive6Source]; -- } -- -- return; -- } -- -- if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0)) -- { -- LogVerbose(@"No data available to receive..."); -- -- if (socket4FDBytesAvailable == 0) { -- [self resumeReceive4Source]; -- } -- if (socket6FDBytesAvailable == 0) { -- [self resumeReceive6Source]; -- } -- -- return; -- } -- -- // Figure out if we should receive on socket4 or socket6 -- -- BOOL doReceive4; -- -- if (flags & kDidConnect) -- { -- // Connected socket -- -- doReceive4 = (socket4FD != SOCKET_NULL); -- } -- else -- { -- // Non-Connected socket -- -- if (socket4FDBytesAvailable > 0) -- { -- if (socket6FDBytesAvailable > 0) -- { -- // Bytes available on socket4 & socket6 -- -- doReceive4 = (flags & kFlipFlop) ? YES : NO; -- -- flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit) -- } -- else { -- // Bytes available on socket4, but not socket6 -- doReceive4 = YES; -- } -- } -- else { -- // Bytes available on socket6, but not socket4 -- doReceive4 = NO; -- } -- } -- -- // Perform socket IO -- -- ssize_t result = 0; -- -- NSData *data = nil; -- NSData *addr4 = nil; -- NSData *addr6 = nil; -- -- if (doReceive4) -- { -- NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic"); -- LogVerbose(@"Receiving on IPv4"); -- -- struct sockaddr_in sockaddr4; -- socklen_t sockaddr4len = sizeof(sockaddr4); -- -- // #222: GCD does not necessarily return the size of an entire UDP packet -- // from dispatch_source_get_data(), so we must use the maximum packet size. -- size_t bufSize = max4ReceiveSize; -- void *buf = malloc(bufSize); -- -- result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len); -- LogVerbose(@"recvfrom(socket4FD) = %i", (int)result); -- -- if (result > 0) -- { -- if ((size_t)result >= socket4FDBytesAvailable) -- socket4FDBytesAvailable = 0; -- else -- socket4FDBytesAvailable -= result; -- -- if ((size_t)result != bufSize) { -- buf = realloc(buf, result); -- } -- -- data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; -- addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len]; -- } -- else -- { -- LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]); -- socket4FDBytesAvailable = 0; -- free(buf); -- } -- } -- else -- { -- NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic"); -- LogVerbose(@"Receiving on IPv6"); -- -- struct sockaddr_in6 sockaddr6; -- socklen_t sockaddr6len = sizeof(sockaddr6); -- -- // #222: GCD does not necessarily return the size of an entire UDP packet -- // from dispatch_source_get_data(), so we must use the maximum packet size. -- size_t bufSize = max6ReceiveSize; -- void *buf = malloc(bufSize); -- -- result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len); -- LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result); -- -- if (result > 0) -- { -- if ((size_t)result >= socket6FDBytesAvailable) -- socket6FDBytesAvailable = 0; -- else -- socket6FDBytesAvailable -= result; -- -- if ((size_t)result != bufSize) { -- buf = realloc(buf, result); -- } -- -- data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES]; -- addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len]; -- } -- else -- { -- LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]); -- socket6FDBytesAvailable = 0; -- free(buf); -- } -- } -- -- -- BOOL waitingForSocket = NO; -- BOOL notifiedDelegate = NO; -- BOOL ignored = NO; -- -- NSError *socketError = nil; -- -- if (result == 0) -- { -- waitingForSocket = YES; -- } -- else if (result < 0) -- { -- if (errno == EAGAIN) -- waitingForSocket = YES; -- else -- socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"]; -- } -- else -- { -- if (flags & kDidConnect) -- { -- if (addr4 && ![self isConnectedToAddress4:addr4]) -- ignored = YES; -- if (addr6 && ![self isConnectedToAddress6:addr6]) -- ignored = YES; -- } -- -- NSData *addr = (addr4 != nil) ? addr4 : addr6; -- -- if (!ignored) -- { -- if (receiveFilterBlock && receiveFilterQueue) -- { -- // Run data through filter, and if approved, notify delegate -- -- __block id filterContext = nil; -- __block BOOL allowed = NO; -- -- if (receiveFilterAsync) -- { -- pendingFilterOperations++; -- dispatch_async(receiveFilterQueue, ^{ @autoreleasepool { -- -- allowed = receiveFilterBlock(data, addr, &filterContext); -- -- // Transition back to socketQueue to get the current delegate / delegateQueue -- dispatch_async(socketQueue, ^{ @autoreleasepool { -- -- pendingFilterOperations--; -- -- if (allowed) -- { -- [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; -- } -- else -- { -- LogVerbose(@"received packet silently dropped by receiveFilter"); -- } -- -- if (flags & kReceiveOnce) -- { -- if (allowed) -- { -- // The delegate has been notified, -- // so our receive once operation has completed. -- flags &= ~kReceiveOnce; -- } -- else if (pendingFilterOperations == 0) -- { -- // All pending filter operations have completed, -- // and none were allowed through. -- // Our receive once operation hasn't completed yet. -- [self doReceive]; -- } -- } -- }}); -- }}); -- } -- else // if (!receiveFilterAsync) -- { -- dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool { -- -- allowed = receiveFilterBlock(data, addr, &filterContext); -- }}); -- -- if (allowed) -- { -- [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext]; -- notifiedDelegate = YES; -- } -- else -- { -- LogVerbose(@"received packet silently dropped by receiveFilter"); -- ignored = YES; -- } -- } -- } -- else // if (!receiveFilterBlock || !receiveFilterQueue) -- { -- [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil]; -- notifiedDelegate = YES; -- } -- } -- } -- -- if (waitingForSocket) -- { -- // Wait for a notification of available data. -- -- if (socket4FDBytesAvailable == 0) { -- [self resumeReceive4Source]; -- } -- if (socket6FDBytesAvailable == 0) { -- [self resumeReceive6Source]; -- } -- } -- else if (socketError) -- { -- [self closeWithError:socketError]; -- } -- else -- { -- if (flags & kReceiveContinuous) -- { -- // Continuous receive mode -- [self doReceive]; -- } -- else -- { -- // One-at-a-time receive mode -- if (notifiedDelegate) -- { -- // The delegate has been notified (no set filter). -- // So our receive once operation has completed. -- flags &= ~kReceiveOnce; -- } -- else if (ignored) -- { -- [self doReceive]; -- } -- else -- { -- // Waiting on asynchronous receive filter... -- } -- } -- } --} -- --- (void)doReceiveEOF --{ -- LogTrace(); -- -- [self closeWithError:[self socketClosedError]]; --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Closing --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --- (void)closeWithError:(NSError *)error --{ -- LogVerbose(@"closeWithError: %@", error); -- -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if (currentSend) [self endCurrentSend]; -- -- [sendQueue removeAllObjects]; -- -- // If a socket has been created, we should notify the delegate. -- BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO; -- -- // Close all sockets, send/receive sources, cfstreams, etc --#if TARGET_OS_IPHONE -- [self removeStreamsFromRunLoop]; -- [self closeReadAndWriteStreams]; --#endif -- [self closeSockets]; -- -- // Clear all flags (config remains as is) -- flags = 0; -- -- if (shouldCallDelegate) -- { -- [self notifyDidCloseWithError:error]; -- } --} -- --- (void)close --{ -- LogTrace(); -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- [self closeWithError:nil]; -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); --} -- --- (void)closeAfterSending --{ -- LogTrace(); -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- flags |= kCloseAfterSends; -- -- if (currentSend == nil && [sendQueue count] == 0) -- { -- [self closeWithError:nil]; -- } -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark CFStream --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --#if TARGET_OS_IPHONE -- --static NSThread *listenerThread; -- --+ (void)ignore:(id)_ --{} -- --+ (void)startListenerThreadIfNeeded --{ -- static dispatch_once_t predicate; -- dispatch_once(&predicate, ^{ -- -- listenerThread = [[NSThread alloc] initWithTarget:self -- selector:@selector(listenerThread) -- object:nil]; -- [listenerThread start]; -- }); --} -- --+ (void)listenerThread --{ -- @autoreleasepool { -- -- [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName]; -- -- LogInfo(@"ListenerThread: Started"); -- -- // We can't run the run loop unless it has an associated input source or a timer. -- // So we'll just create a timer that will never fire - unless the server runs for a decades. -- [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] -- target:self -- selector:@selector(ignore:) -- userInfo:nil -- repeats:YES]; -- -- [[NSRunLoop currentRunLoop] run]; -- -- LogInfo(@"ListenerThread: Stopped"); -- } --} -- --+ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket --{ -- LogTrace(); -- NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); -- -- CFRunLoopRef runLoop = CFRunLoopGetCurrent(); -- -- if (asyncUdpSocket->readStream4) -- CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->readStream6) -- CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->writeStream4) -- CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->writeStream6) -- CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); --} -- --+ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket --{ -- LogTrace(); -- NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread"); -- -- CFRunLoopRef runLoop = CFRunLoopGetCurrent(); -- -- if (asyncUdpSocket->readStream4) -- CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->readStream6) -- CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->writeStream4) -- CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode); -- -- if (asyncUdpSocket->writeStream6) -- CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode); --} -- --static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo) --{ -- @autoreleasepool { -- GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; -- -- switch(type) -- { -- case kCFStreamEventOpenCompleted: -- { -- LogCVerbose(@"CFReadStreamCallback - Open"); -- break; -- } -- case kCFStreamEventHasBytesAvailable: -- { -- LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable"); -- break; -- } -- case kCFStreamEventErrorOccurred: -- case kCFStreamEventEndEncountered: -- { -- NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream); -- if (error == nil && type == kCFStreamEventEndEncountered) -- { -- error = [asyncUdpSocket socketClosedError]; -- } -- -- dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { -- -- LogCVerbose(@"CFReadStreamCallback - %@", -- (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); -- -- if (stream != asyncUdpSocket->readStream4 && -- stream != asyncUdpSocket->readStream6 ) -- { -- LogCVerbose(@"CFReadStreamCallback - Ignored"); -- return_from_block; -- } -- -- [asyncUdpSocket closeWithError:error]; -- -- }}); -- -- break; -- } -- default: -- { -- LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type); -- } -- } -- } --} -- --static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) --{ -- @autoreleasepool { -- GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo; -- -- switch(type) -- { -- case kCFStreamEventOpenCompleted: -- { -- LogCVerbose(@"CFWriteStreamCallback - Open"); -- break; -- } -- case kCFStreamEventCanAcceptBytes: -- { -- LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes"); -- break; -- } -- case kCFStreamEventErrorOccurred: -- case kCFStreamEventEndEncountered: -- { -- NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream); -- if (error == nil && type == kCFStreamEventEndEncountered) -- { -- error = [asyncUdpSocket socketClosedError]; -- } -- -- dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool { -- -- LogCVerbose(@"CFWriteStreamCallback - %@", -- (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered"); -- -- if (stream != asyncUdpSocket->writeStream4 && -- stream != asyncUdpSocket->writeStream6 ) -- { -- LogCVerbose(@"CFWriteStreamCallback - Ignored"); -- return_from_block; -- } -- -- [asyncUdpSocket closeWithError:error]; -- -- }}); -- -- break; -- } -- default: -- { -- LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type); -- } -- } -- } --} -- --- (BOOL)createReadAndWriteStreams:(NSError **)errPtr --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- NSError *err = nil; -- -- if (readStream4 || writeStream4 || readStream6 || writeStream6) -- { -- // Streams already created -- return YES; -- } -- -- if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL) -- { -- err = [self otherError:@"Cannot create streams without a file descriptor"]; -- goto Failed; -- } -- -- // Create streams -- -- LogVerbose(@"Creating read and write stream(s)..."); -- -- if (socket4FD != SOCKET_NULL) -- { -- CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4); -- if (!readStream4 || !writeStream4) -- { -- err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"]; -- goto Failed; -- } -- } -- -- if (socket6FD != SOCKET_NULL) -- { -- CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6); -- if (!readStream6 || !writeStream6) -- { -- err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"]; -- goto Failed; -- } -- } -- -- // Ensure the CFStream's don't close our underlying socket -- -- CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); -- CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); -- -- CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); -- CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); -- -- return YES; -- --Failed: -- if (readStream4) -- { -- CFReadStreamClose(readStream4); -- CFRelease(readStream4); -- readStream4 = NULL; -- } -- if (writeStream4) -- { -- CFWriteStreamClose(writeStream4); -- CFRelease(writeStream4); -- writeStream4 = NULL; -- } -- if (readStream6) -- { -- CFReadStreamClose(readStream6); -- CFRelease(readStream6); -- readStream6 = NULL; -- } -- if (writeStream6) -- { -- CFWriteStreamClose(writeStream6); -- CFRelease(writeStream6); -- writeStream6 = NULL; -- } -- -- if (errPtr) -- *errPtr = err; -- -- return NO; --} -- --- (BOOL)registerForStreamCallbacks:(NSError **)errPtr --{ -- LogTrace(); -- -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); -- -- NSError *err = nil; -- -- streamContext.version = 0; -- streamContext.info = (__bridge void *)self; -- streamContext.retain = nil; -- streamContext.release = nil; -- streamContext.copyDescription = nil; -- -- CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; -- CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; -- --// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable); --// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes); -- -- if (socket4FD != SOCKET_NULL) -- { -- if (readStream4 == NULL || writeStream4 == NULL) -- { -- err = [self otherError:@"Read/Write stream4 is null"]; -- goto Failed; -- } -- -- BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext); -- BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext); -- -- if (!r1 || !r2) -- { -- err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"]; -- goto Failed; -- } -- } -- -- if (socket6FD != SOCKET_NULL) -- { -- if (readStream6 == NULL || writeStream6 == NULL) -- { -- err = [self otherError:@"Read/Write stream6 is null"]; -- goto Failed; -- } -- -- BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext); -- BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext); -- -- if (!r1 || !r2) -- { -- err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"]; -- goto Failed; -- } -- } -- -- return YES; -- --Failed: -- if (readStream4) { -- CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); -- } -- if (writeStream4) { -- CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); -- } -- if (readStream6) { -- CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); -- } -- if (writeStream6) { -- CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); -- } -- -- if (errPtr) *errPtr = err; -- return NO; --} -- --- (BOOL)addStreamsToRunLoop:(NSError **)errPtr --{ -- LogTrace(); -- -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); -- -- if (!(flags & kAddedStreamListener)) -- { -- [[self class] startListenerThreadIfNeeded]; -- [[self class] performSelector:@selector(addStreamListener:) -- onThread:listenerThread -- withObject:self -- waitUntilDone:YES]; -- -- flags |= kAddedStreamListener; -- } -- -- return YES; --} -- --- (BOOL)openStreams:(NSError **)errPtr --{ -- LogTrace(); -- -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null"); -- -- NSError *err = nil; -- -- if (socket4FD != SOCKET_NULL) -- { -- BOOL r1 = CFReadStreamOpen(readStream4); -- BOOL r2 = CFWriteStreamOpen(writeStream4); -- -- if (!r1 || !r2) -- { -- err = [self otherError:@"Error in CFStreamOpen() [IPv4]"]; -- goto Failed; -- } -- } -- -- if (socket6FD != SOCKET_NULL) -- { -- BOOL r1 = CFReadStreamOpen(readStream6); -- BOOL r2 = CFWriteStreamOpen(writeStream6); -- -- if (!r1 || !r2) -- { -- err = [self otherError:@"Error in CFStreamOpen() [IPv6]"]; -- goto Failed; -- } -- } -- -- return YES; -- --Failed: -- if (errPtr) *errPtr = err; -- return NO; --} -- --- (void)removeStreamsFromRunLoop --{ -- LogTrace(); -- NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue"); -- -- if (flags & kAddedStreamListener) -- { -- [[self class] performSelector:@selector(removeStreamListener:) -- onThread:listenerThread -- withObject:self -- waitUntilDone:YES]; -- -- flags &= ~kAddedStreamListener; -- } --} -- --- (void)closeReadAndWriteStreams --{ -- LogTrace(); -- -- if (readStream4) -- { -- CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL); -- CFReadStreamClose(readStream4); -- CFRelease(readStream4); -- readStream4 = NULL; -- } -- if (writeStream4) -- { -- CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL); -- CFWriteStreamClose(writeStream4); -- CFRelease(writeStream4); -- writeStream4 = NULL; -- } -- if (readStream6) -- { -- CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL); -- CFReadStreamClose(readStream6); -- CFRelease(readStream6); -- readStream6 = NULL; -- } -- if (writeStream6) -- { -- CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL); -- CFWriteStreamClose(writeStream6); -- CFRelease(writeStream6); -- writeStream6 = NULL; -- } --} -- --#endif -- --- (void)applicationWillEnterForeground:(NSNotification *)notification --{ -- LogTrace(); -- -- // If the application was backgrounded, then iOS may have shut down our sockets. -- // So we take a quick look to see if any of them received an EOF. -- -- dispatch_block_t block = ^{ @autoreleasepool { -- -- [self resumeReceive4Source]; -- [self resumeReceive6Source]; -- }}; -- -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_async(socketQueue, block); --} -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Advanced --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --/** -- * See header file for big discussion of this method. -- **/ --- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue --{ -- void *nonNullUnusedPointer = (__bridge void *)self; -- dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL); --} -- --/** -- * See header file for big discussion of this method. -- **/ --- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue --{ -- dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL); --} -- --- (void)performBlock:(dispatch_block_t)block --{ -- if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- block(); -- else -- dispatch_sync(socketQueue, block); --} -- --- (int)socketFD --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return SOCKET_NULL; -- } -- -- if (socket4FD != SOCKET_NULL) -- return socket4FD; -- else -- return socket6FD; --} -- --- (int)socket4FD --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return SOCKET_NULL; -- } -- -- return socket4FD; --} -- --- (int)socket6FD --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return SOCKET_NULL; -- } -- -- return socket6FD; --} -- --#if TARGET_OS_IPHONE -- --- (CFReadStreamRef)readStream --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return NULL; -- } -- -- NSError *err = nil; -- if (![self createReadAndWriteStreams:&err]) -- { -- LogError(@"Error creating CFStream(s): %@", err); -- return NULL; -- } -- -- // Todo... -- -- if (readStream4) -- return readStream4; -- else -- return readStream6; --} -- --- (CFWriteStreamRef)writeStream --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return NULL; -- } -- -- NSError *err = nil; -- if (![self createReadAndWriteStreams:&err]) -- { -- LogError(@"Error creating CFStream(s): %@", err); -- return NULL; -- } -- -- if (writeStream4) -- return writeStream4; -- else -- return writeStream6; --} -- --- (BOOL)enableBackgroundingOnSockets --{ -- if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) -- { -- LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation", -- THIS_FILE, THIS_METHOD); -- return NO; -- } -- -- // Why is this commented out? -- // See comments below. -- --// NSError *err = nil; --// if (![self createReadAndWriteStreams:&err]) --// { --// LogError(@"Error creating CFStream(s): %@", err); --// return NO; --// } --// --// LogVerbose(@"Enabling backgrouding on socket"); --// --// BOOL r1, r2; --// --// if (readStream4 && writeStream4) --// { --// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); --// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); --// --// if (!r1 || !r2) --// { --// LogError(@"Error setting voip type (IPv4)"); --// return NO; --// } --// } --// --// if (readStream6 && writeStream6) --// { --// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); --// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); --// --// if (!r1 || !r2) --// { --// LogError(@"Error setting voip type (IPv6)"); --// return NO; --// } --// } --// --// return YES; -- -- // The above code will actually appear to work. -- // The methods will return YES, and everything will appear fine. -- // -- // One tiny problem: the sockets will still get closed when the app gets backgrounded. -- // -- // Apple does not officially support backgrounding UDP sockets. -- -- return NO; --} -- --#endif -- --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// --#pragma mark Class Methods --//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -- --+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 --{ -- char addrBuf[INET_ADDRSTRLEN]; -- -- if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) -- { -- addrBuf[0] = '\0'; -- } -- -- return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; --} -- --+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 --{ -- char addrBuf[INET6_ADDRSTRLEN]; -- -- if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) -- { -- addrBuf[0] = '\0'; -- } -- -- return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; --} -- --+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4 --{ -- return ntohs(pSockaddr4->sin_port); --} -- --+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6 --{ -- return ntohs(pSockaddr6->sin6_port); --} -- --+ (NSString *)hostFromAddress:(NSData *)address --{ -- NSString *host = nil; -- [self getHost:&host port:NULL family:NULL fromAddress:address]; -- -- return host; --} -- --+ (uint16_t)portFromAddress:(NSData *)address --{ -- uint16_t port = 0; -- [self getHost:NULL port:&port family:NULL fromAddress:address]; -- -- return port; --} -- --+ (int)familyFromAddress:(NSData *)address --{ -- int af = AF_UNSPEC; -- [self getHost:NULL port:NULL family:&af fromAddress:address]; -- -- return af; --} -- --+ (BOOL)isIPv4Address:(NSData *)address --{ -- int af = AF_UNSPEC; -- [self getHost:NULL port:NULL family:&af fromAddress:address]; -- -- return (af == AF_INET); --} -- --+ (BOOL)isIPv6Address:(NSData *)address --{ -- int af = AF_UNSPEC; -- [self getHost:NULL port:NULL family:&af fromAddress:address]; -- -- return (af == AF_INET6); --} -- --+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address --{ -- return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address]; --} -- --+ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address --{ -- if ([address length] >= sizeof(struct sockaddr)) -- { -- const struct sockaddr *addrX = (const struct sockaddr *)[address bytes]; -- -- if (addrX->sa_family == AF_INET) -- { -- if ([address length] >= sizeof(struct sockaddr_in)) -- { -- const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addrX; -- -- if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4]; -- if (portPtr) *portPtr = [self portFromSockaddr4:addr4]; -- if (afPtr) *afPtr = AF_INET; -- -- return YES; -- } -- } -- else if (addrX->sa_family == AF_INET6) -- { -- if ([address length] >= sizeof(struct sockaddr_in6)) -- { -- const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addrX; -- -- if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6]; -- if (portPtr) *portPtr = [self portFromSockaddr6:addr6]; -- if (afPtr) *afPtr = AF_INET6; -- -- return YES; -- } -- } -- } -- -- if (hostPtr) *hostPtr = nil; -- if (portPtr) *portPtr = 0; -- if (afPtr) *afPtr = AF_UNSPEC; -- -- return NO; --} -- --@end -diff --git a/node_modules/react-native-udp/ios/UdpSockets.xcodeproj/project.pbxproj b/node_modules/react-native-udp/ios/UdpSockets.xcodeproj/project.pbxproj -index fa03f3b..935745e 100644 ---- a/node_modules/react-native-udp/ios/UdpSockets.xcodeproj/project.pbxproj -+++ b/node_modules/react-native-udp/ios/UdpSockets.xcodeproj/project.pbxproj -@@ -34,8 +34,6 @@ - 7350006A1AFF9AB600ED3C82 /* UdpSocketClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UdpSocketClient.m; sourceTree = ""; }; - 73D9377C1AFF9EBE00450142 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; - 73D9377E1AFF9F6E00450142 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; -- 73D937A71AFFA5C900450142 /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncUdpSocket.h; path = CocoaAsyncSocket/GCDAsyncUdpSocket.h; sourceTree = ""; }; -- 73D937A81AFFA5C900450142 /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncUdpSocket.m; path = CocoaAsyncSocket/GCDAsyncUdpSocket.m; sourceTree = ""; }; - /* End PBXFileReference section */ - - /* Begin PBXFrameworksBuildPhase section */ -diff --git a/node_modules/react-native-udp/react-native-udp.podspec b/node_modules/react-native-udp/react-native-udp.podspec -index e396fe0..e591931 100644 ---- a/node_modules/react-native-udp/react-native-udp.podspec -+++ b/node_modules/react-native-udp/react-native-udp.podspec -@@ -13,5 +13,6 @@ Pod::Spec.new do |s| - s.source = { :git => package_json["repository"]["url"].gsub(/(http.*)/).first, :tag => "v#{s.version}" } - s.source_files = 'ios/**/*.{h,m}' - s.dependency 'React' -+ s.dependency "CocoaAsyncSocket" - - end diff --git a/scripts/eas-build-pre-install.sh b/scripts/eas-build-pre-install.sh index 94ca391336d..986296d8c57 100755 --- a/scripts/eas-build-pre-install.sh +++ b/scripts/eas-build-pre-install.sh @@ -1,39 +1,40 @@ #!/bin/bash -echo "PRE INSTALLING" - -mkdir -p ~/.ssh - -# Real origin URL is lost during the packaging process, so if your -# submodules are defined using relative urls in .gitmodules then -# you need to restore it with: -# -# git remote set-url origin git@github.com:example/repo.git - -# restore private key from env variable and generate public key -echo "$SSH_KEY_BASE64" | base64 -d > ~/.ssh/eas_internals -chmod 600 ~/.ssh/eas_internals -ssh-keygen -y -f ~/.ssh/eas_internals > ~/.ssh/eas_internals.pub - -# add your git provider to the list of known hosts -ssh-keyscan github.com >> ~/.ssh/known_hosts - -if [ -n "$RAINBOW_SCRIPTS_INTERNALS" ]; then - git clone $RAINBOW_SCRIPTS_INTERNALS +echo "🌈 Setting up environment" +if [ -f .env ]; then + source .env +else + echo "🚨 .env file not found" + exit 1 fi -if [ -n "$RAINBOW_INTERNALS" ]; then - git clone $RAINBOW_INTERNALS -fi +echo "🌈 Pulling latest rainbow-scripts code from github" +git clone https://$RAINBOW_SCRIPTS_TOKEN$RAINBOW_SCRIPTS_INTERNALS +cd rainbow-scripts +git checkout prebuild +cd .. + +echo "🌈 Running yarn setup..." +yarn setup if [ "$EAS_BUILD_PLATFORM" = "ios" ]; then - eval $RAINBOW_SCRIPTS_APP_IOS_PREBUILD_HOOK > /dev/null 2>&1; + echo "🌈 Executing iOS prebuild.sh script" + bash $RAINBOW_SCRIPTS_APP_IOS_PREBUILD_HOOK; echo "✅ executed ios prebuild hook" fi if [ "$EAS_BUILD_PLATFORM" = "android" ]; then - eval $RAINBOW_SCRIPTS_APP_ANDROID_PREBUILD_HOOK > /dev/null 2>&1; + bash $RAINBOW_SCRIPTS_APP_ANDROID_PREBUILD_HOOK; + + echo "🌈 Downloading Google Services JSON" + curl -H "Authorization: token $RAINBOW_SCRIPTS_TOKEN" -L $RAINBOW_GOOGLE_SERVICES_JSON -o android/app/google-services.json + + if [ "$EAS_BUILD_PLATFORM" = "linux" ]; then + echo "🌈 apt-get libsecret-tools" + sudo apt-get -y install libsecret-tools + fi + echo "✅ executed android prebuild hook" fi -yarn setup \ No newline at end of file +echo "🌈 Finished preinstallation..." diff --git a/scripts/setup-env.sh b/scripts/setup-env.sh new file mode 100755 index 00000000000..9f13d52f6c4 --- /dev/null +++ b/scripts/setup-env.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "🌈 Setting up environment" +if [ -f .env ]; then + source .env +else + echo "🚨 .env file not found" + exit 1 +fi + +echo "🌈 Creating .easignore" +bash $RAINBOW_SCRIPTS_APP_EASIGNORE_HOOK; + +echo "🌈 Finished preboot..." diff --git a/shim.js b/shim.js index 14dcce93e3b..5201d4cff03 100644 --- a/shim.js +++ b/shim.js @@ -1,4 +1,5 @@ import 'react-native-get-random-values'; +import 'react-native-url-polyfill/auto'; import '@ethersproject/shims'; import AsyncStorage from '@react-native-async-storage/async-storage'; import ReactNative from 'react-native'; diff --git a/src/App.js b/src/App.js index cee9647c9d8..a5c6f9e420d 100644 --- a/src/App.js +++ b/src/App.js @@ -18,7 +18,6 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; import { connect, Provider as ReduxProvider } from 'react-redux'; import { RecoilRoot } from 'recoil'; -import { runCampaignChecks } from './campaigns/campaignChecks'; import PortalConsumer from './components/PortalConsumer'; import ErrorBoundary from './components/error-boundary/ErrorBoundary'; import { OfflineToast } from './components/toasts'; @@ -33,10 +32,7 @@ import { Playground } from './design-system/playground/Playground'; import { TransactionType } from './entities'; import appEvents from './handlers/appEvents'; import handleDeeplink from './handlers/deeplinks'; -import { - runFeatureAndCampaignChecks, - runWalletBackupStatusChecks, -} from './handlers/walletReadyEvents'; +import { runWalletBackupStatusChecks } from './handlers/walletReadyEvents'; import { getCachedProviderForNetwork, isHardHat, @@ -84,6 +80,9 @@ import { initListeners as initWalletConnectListeners } from '@/walletConnect'; import { saveFCMToken } from '@/notifications/tokens'; import branch from 'react-native-branch'; import { initializeReservoirClient } from '@/resources/reservoir/client'; +import { ReviewPromptAction } from '@/storage/schema'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { RemotePromoSheetProvider } from '@/components/remote-promo-sheet/RemotePromoSheetProvider'; if (__DEV__) { reactNativeDisableYellowBox && LogBox.ignoreAllLogs(); @@ -168,6 +167,17 @@ class OldApp extends Component { * Needs to be called AFTER FCM token is loaded */ initWalletConnectListeners(); + + /** + * Launch the review prompt after the app is launched + * This is to avoid the review prompt showing up when the app is + * launched and not shown yet. + */ + InteractionManager.runAfterInteractions(() => { + setTimeout(() => { + handleReviewPromptAction(ReviewPromptAction.TimesLaunchedSinceInstall); + }, 10_000); + }); } componentDidUpdate(prevProps) { @@ -175,15 +185,6 @@ class OldApp extends Component { // Everything we need to do after the wallet is ready goes here logger.info('✅ Wallet ready!'); runWalletBackupStatusChecks(); - - InteractionManager.runAfterInteractions(() => { - setTimeout(() => { - if (IS_TESTING === 'true') { - return; - } - runFeatureAndCampaignChecks(); - }, 2000); - }); } } @@ -272,13 +273,15 @@ class OldApp extends Component { {this.state.initialRoute && ( - - - - + + + + + + )} @@ -332,6 +335,21 @@ function Root() { */ analyticsV2.identify({}); + const isReviewInitialized = ls.review.get(['initialized']); + if (!isReviewInitialized) { + ls.review.set(['hasReviewed'], false); + ls.review.set( + ['actions'], + Object.values(ReviewPromptAction).map(action => ({ + id: action, + numOfTimesDispatched: 0, + })) + ); + + ls.review.set(['timeOfLastPrompt'], 0); + ls.review.set(['initialized'], true); + } + /** * We previously relied on the existence of a deviceId on keychain to * determine if a user was new or not. For backwards compat, we do this diff --git a/src/analytics/event.ts b/src/analytics/event.ts index 5af645fe65a..809d161da0c 100644 --- a/src/analytics/event.ts +++ b/src/analytics/event.ts @@ -13,6 +13,8 @@ export const event = { appStateChange: 'State change', analyticsTrackingDisabled: 'analytics_tracking.disabled', analyticsTrackingEnabled: 'analytics_tracking.enabled', + promoSheetShown: 'promo_sheet.shown', + promoSheetDismissed: 'promo_sheet.dismissed', swapSubmitted: 'Submitted Swap', // notification promo sheet was shown notificationsPromoShown: 'notifications_promo.shown', @@ -120,6 +122,14 @@ export type EventProperties = { inputCurrencySymbol: string; outputCurrencySymbol: string; }; + [event.promoSheetShown]: { + campaign: string; + time_viewed: number; + }; + [event.promoSheetDismissed]: { + campaign: string; + time_viewed: number; + }; [event.notificationsPromoShown]: undefined; [event.notificationsPromoPermissionsBlocked]: undefined; [event.notificationsPromoPermissionsGranted]: undefined; diff --git a/src/assets/badges/arbitrum.png b/src/assets/badges/arbitrum.png new file mode 100644 index 00000000000..ad5c0420ad0 Binary files /dev/null and b/src/assets/badges/arbitrum.png differ diff --git a/src/assets/badges/arbitrum@2x.png b/src/assets/badges/arbitrum@2x.png new file mode 100644 index 00000000000..697bc2f6ec4 Binary files /dev/null and b/src/assets/badges/arbitrum@2x.png differ diff --git a/src/assets/badges/arbitrum@3x.png b/src/assets/badges/arbitrum@3x.png new file mode 100644 index 00000000000..4153e6706f2 Binary files /dev/null and b/src/assets/badges/arbitrum@3x.png differ diff --git a/src/assets/badges/base.png b/src/assets/badges/base.png new file mode 100644 index 00000000000..8d365d203f2 Binary files /dev/null and b/src/assets/badges/base.png differ diff --git a/src/assets/badges/base@2x.png b/src/assets/badges/base@2x.png new file mode 100644 index 00000000000..1aded5c29f9 Binary files /dev/null and b/src/assets/badges/base@2x.png differ diff --git a/src/assets/badges/base@3x.png b/src/assets/badges/base@3x.png new file mode 100644 index 00000000000..570dc2e8618 Binary files /dev/null and b/src/assets/badges/base@3x.png differ diff --git a/src/assets/badges/bsc.png b/src/assets/badges/bsc.png new file mode 100644 index 00000000000..009bae7a844 Binary files /dev/null and b/src/assets/badges/bsc.png differ diff --git a/src/assets/badges/bsc@2x.png b/src/assets/badges/bsc@2x.png new file mode 100644 index 00000000000..f164fd04e32 Binary files /dev/null and b/src/assets/badges/bsc@2x.png differ diff --git a/src/assets/badges/bsc@3x.png b/src/assets/badges/bsc@3x.png new file mode 100644 index 00000000000..0253b66c648 Binary files /dev/null and b/src/assets/badges/bsc@3x.png differ diff --git a/src/assets/badges/ethereum.png b/src/assets/badges/ethereum.png new file mode 100644 index 00000000000..ce612bb3da8 Binary files /dev/null and b/src/assets/badges/ethereum.png differ diff --git a/src/assets/badges/ethereum@2x.png b/src/assets/badges/ethereum@2x.png new file mode 100644 index 00000000000..c3aa91dd13f Binary files /dev/null and b/src/assets/badges/ethereum@2x.png differ diff --git a/src/assets/badges/ethereum@3x.png b/src/assets/badges/ethereum@3x.png new file mode 100644 index 00000000000..237f8a091cd Binary files /dev/null and b/src/assets/badges/ethereum@3x.png differ diff --git a/src/assets/badges/optimism.png b/src/assets/badges/optimism.png new file mode 100644 index 00000000000..36d6cf2e73f Binary files /dev/null and b/src/assets/badges/optimism.png differ diff --git a/src/assets/badges/optimism@2x.png b/src/assets/badges/optimism@2x.png new file mode 100644 index 00000000000..81f4ced3fb9 Binary files /dev/null and b/src/assets/badges/optimism@2x.png differ diff --git a/src/assets/badges/optimism@3x.png b/src/assets/badges/optimism@3x.png new file mode 100644 index 00000000000..d5dab748c01 Binary files /dev/null and b/src/assets/badges/optimism@3x.png differ diff --git a/src/assets/badges/polygon.png b/src/assets/badges/polygon.png new file mode 100644 index 00000000000..e0a9fa08e9c Binary files /dev/null and b/src/assets/badges/polygon.png differ diff --git a/src/assets/badges/polygon@2x.png b/src/assets/badges/polygon@2x.png new file mode 100644 index 00000000000..9ff81cf978b Binary files /dev/null and b/src/assets/badges/polygon@2x.png differ diff --git a/src/assets/badges/polygon@3x.png b/src/assets/badges/polygon@3x.png new file mode 100644 index 00000000000..9e3bc2e7d04 Binary files /dev/null and b/src/assets/badges/polygon@3x.png differ diff --git a/src/assets/badges/xdai.png b/src/assets/badges/xdai.png new file mode 100644 index 00000000000..f62aac06a14 Binary files /dev/null and b/src/assets/badges/xdai.png differ diff --git a/src/assets/badges/xdai@2x.png b/src/assets/badges/xdai@2x.png new file mode 100644 index 00000000000..4ea6a80914c Binary files /dev/null and b/src/assets/badges/xdai@2x.png differ diff --git a/src/assets/badges/xdai@3x.png b/src/assets/badges/xdai@3x.png new file mode 100644 index 00000000000..2969bc76ba9 Binary files /dev/null and b/src/assets/badges/xdai@3x.png differ diff --git a/src/assets/badges/zora.png b/src/assets/badges/zora.png new file mode 100644 index 00000000000..2310c3dca62 Binary files /dev/null and b/src/assets/badges/zora.png differ diff --git a/src/assets/badges/zora@2x.png b/src/assets/badges/zora@2x.png new file mode 100644 index 00000000000..5256774e273 Binary files /dev/null and b/src/assets/badges/zora@2x.png differ diff --git a/src/assets/badges/zora@3x.png b/src/assets/badges/zora@3x.png new file mode 100644 index 00000000000..3504d713a84 Binary files /dev/null and b/src/assets/badges/zora@3x.png differ diff --git a/src/campaigns/swapsPromoCampaign.ts b/src/campaigns/swapsPromoCampaign.ts deleted file mode 100644 index 0b33ba1b760..00000000000 --- a/src/campaigns/swapsPromoCampaign.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { MMKV } from 'react-native-mmkv'; -import { - Campaign, - CampaignCheckType, - CampaignKey, - GenericCampaignCheckResponse, -} from './campaignChecks'; -import { EthereumAddress, RainbowTransaction } from '@/entities'; -import { Network } from '@/helpers/networkTypes'; -import WalletTypes from '@/helpers/walletTypes'; -import { RainbowWallet } from '@/model/wallet'; -import { Navigation } from '@/navigation'; -import { ethereumUtils, logger } from '@/utils'; -import store from '@/redux/store'; -import Routes from '@/navigation/routesNames'; -import { STORAGE_IDS } from '@/model/mmkv'; -import { RainbowNetworks } from '@/networks'; - -// Rainbow Router -const RAINBOW_ROUTER_ADDRESS: EthereumAddress = - '0x00000000009726632680fb29d3f7a9734e3010e2'; - -const swapsLaunchDate = new Date('2022-07-26'); -const isAfterSwapsLaunch = (tx: RainbowTransaction): boolean => { - if (tx.minedAt) { - const txDate = new Date(tx.minedAt * 1000); - return txDate > swapsLaunchDate; - } - return false; -}; - -const isSwapTx = (tx: RainbowTransaction): boolean => - tx?.to?.toLowerCase() === RAINBOW_ROUTER_ADDRESS; - -const mmkv = new MMKV(); - -export const swapsCampaignAction = async () => { - logger.log('Campaign: Showing Swaps Promo'); - - mmkv.set(CampaignKey.swapsLaunch, true); - setTimeout(() => { - logger.log('triggering swaps promo action'); - - Navigation.handleAction(Routes.SWAPS_PROMO_SHEET, {}); - }, 1000); -}; - -export enum SwapsPromoCampaignExclusion { - noAssets = 'no_assets', - alreadySwapped = 'already_swapped', - wrongNetwork = 'wrong_network', -} - -export const swapsCampaignCheck = async (): Promise< - SwapsPromoCampaignExclusion | GenericCampaignCheckResponse -> => { - const hasShownCampaign = mmkv.getBoolean(CampaignKey.swapsLaunch); - const isFirstLaunch = mmkv.getBoolean(STORAGE_IDS.FIRST_APP_LAUNCH); - - const { - selected: currentWallet, - }: { - selected: RainbowWallet | undefined; - } = store.getState().wallets; - - /** - * stop if: - * there's no wallet - * the current wallet is read only - * the campaign has already been activated - * the user is launching Rainbow for the first time - */ - if ( - !currentWallet || - currentWallet.type === WalletTypes.readOnly || - isFirstLaunch || - hasShownCampaign - ) { - return GenericCampaignCheckResponse.nonstarter; - } - - const { - accountAddress, - network: currentNetwork, - }: { - accountAddress: EthereumAddress; - network: Network; - } = store.getState().settings; - - if (currentNetwork !== Network.mainnet) - return SwapsPromoCampaignExclusion.wrongNetwork; - // transactions are loaded from the current wallet - const { transactions } = store.getState().data; - - const networks: Network[] = RainbowNetworks.filter( - network => network.features.swaps - ).map(network => network.value); - - // check native asset balances on networks that support swaps - let hasBalance = false; - await networks.forEach(async (network: Network) => { - const nativeAsset = await ethereumUtils.getNativeAssetForNetwork( - network, - accountAddress - ); - const balance = Number(nativeAsset?.balance?.amount); - if (balance > 0) { - hasBalance = true; - } - }); - - // if the wallet has no native asset balances then stop - if (!hasBalance) return SwapsPromoCampaignExclusion.noAssets; - - const hasSwapped = !!transactions.filter(isAfterSwapsLaunch).find(isSwapTx); - - // if they have not swapped yet, trigger campaign action - if (!hasSwapped) { - SwapsPromoCampaign.action(); - return GenericCampaignCheckResponse.activated; - } - return SwapsPromoCampaignExclusion.alreadySwapped; -}; - -export const SwapsPromoCampaign: Campaign = { - action: async () => await swapsCampaignAction(), - campaignKey: CampaignKey.swapsLaunch, - check: async () => await swapsCampaignCheck(), - checkType: CampaignCheckType.deviceOrWallet, -}; diff --git a/src/components/PromoSheet.tsx b/src/components/PromoSheet.tsx index 42a404aa8a4..0d074b10025 100644 --- a/src/components/PromoSheet.tsx +++ b/src/components/PromoSheet.tsx @@ -1,10 +1,15 @@ import React, { useCallback, useEffect, useReducer } from 'react'; -import { ImageSourcePropType, StatusBar, ImageBackground } from 'react-native'; +import { + ImageSourcePropType, + Dimensions, + StatusBar, + ImageBackground, +} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import MaskedView from '@react-native-masked-view/masked-view'; import { SheetActionButton, SheetHandle, SlackSheet } from '@/components/sheet'; -import { CampaignKey } from '@/campaigns/campaignChecks'; -import { analytics } from '@/analytics'; +import { CampaignKey } from '@/components/remote-promo-sheet/localCampaignChecks'; +import { analyticsV2 } from '@/analytics'; import { AccentColorProvider, Box, @@ -38,7 +43,7 @@ type PromoSheetProps = { backgroundColor: string; accentColor: string; sheetHandleColor?: string; - campaignKey: CampaignKey; + campaignKey: CampaignKey | string; header: string; subHeader: string; primaryButtonProps: SheetActionButtonProps; @@ -74,7 +79,7 @@ export function PromoSheet({ () => () => { if (!activated) { const timeElapsed = (Date.now() - renderedAt) / 1000; - analytics.track('Dismissed Feature Promo', { + analyticsV2.track(analyticsV2.event.promoSheetDismissed, { campaign: campaignKey, time_viewed: timeElapsed, }); @@ -86,12 +91,12 @@ export function PromoSheet({ const primaryButtonOnPress = useCallback(() => { activate(); const timeElapsed = (Date.now() - renderedAt) / 1000; - analytics.track('Activated Feature Promo Action', { + analyticsV2.track(analyticsV2.event.promoSheetShown, { campaign: campaignKey, time_viewed: timeElapsed, }); primaryButtonProps.onPress(); - }, [activate, campaignKey, primaryButtonProps.onPress, renderedAt]); + }, [activate, campaignKey, primaryButtonProps, renderedAt]); // We are not using `isSmallPhone` from `useDimensions` here as we // want to explicitly set a min height. diff --git a/src/components/activity-list/ActivityList.js b/src/components/activity-list/ActivityList.js index db787f4e3da..6de7f11be3a 100644 --- a/src/components/activity-list/ActivityList.js +++ b/src/components/activity-list/ActivityList.js @@ -7,14 +7,13 @@ import ActivityIndicator from '../ActivityIndicator'; import Spinner from '../Spinner'; import { ButtonPressAnimation } from '../animations'; import { CoinRowHeight } from '../coin-row/CoinRow'; -import { TRANSACTION_COIN_ROW_VERTICAL_PADDING } from '../coin-row/TransactionCoinRow'; import Text from '../text/Text'; import ActivityListEmptyState from './ActivityListEmptyState'; import ActivityListHeader from './ActivityListHeader'; -import RecyclerActivityList from './RecyclerActivityList'; import styled from '@/styled-thing'; import { useTheme } from '@/theme'; import { useSectionListScrollToTopContext } from '@/navigation/SectionListScrollToTopContext'; +import { safeAreaInsetValues } from '@/utils'; const sx = StyleSheet.create({ sectionHeader: { @@ -23,6 +22,7 @@ const sx = StyleSheet.create({ }); const ActivityListHeaderHeight = 42; +const TRANSACTION_COIN_ROW_VERTICAL_PADDING = 7; const getItemLayout = sectionListGetItemLayout({ getItemHeight: () => @@ -85,16 +85,13 @@ function ListFooterComponent({ label, onPress }) { } const ActivityList = ({ - addCashAvailable, hasPendingTransaction, header, isEmpty, isLoading, nativeCurrency, - navigation, network, nextPage, - recyclerListView, remainingItemsLabel, requests, sections, @@ -126,17 +123,6 @@ const ActivityList = ({ if (network === networkTypes.mainnet || sections.length) { if (isEmpty && !isLoading) { return {header}; - } else if (recyclerListView) { - return ( - - ); } else { return ( ); diff --git a/src/components/activity-list/RecyclerActivityList.js b/src/components/activity-list/RecyclerActivityList.js deleted file mode 100644 index 9b3c678da95..00000000000 --- a/src/components/activity-list/RecyclerActivityList.js +++ /dev/null @@ -1,196 +0,0 @@ -import lang from 'i18n-js'; -import React, { PureComponent } from 'react'; -import { - DataProvider, - LayoutProvider, - RecyclerListView, -} from 'recyclerlistview'; -import { - ContractInteractionCoinRow, - RequestCoinRow, - TransactionCoinRow, -} from '../coin-row'; -import ListFooter from '../list/ListFooter'; -import { ProfileMasthead } from '../profile'; -import ActivityListHeader from './ActivityListHeader'; -import { TransactionStatusTypes } from '@/entities'; -import { buildTransactionUniqueIdentifier } from '@/helpers/transactions'; -import styled from '@/styled-thing'; -import { deviceUtils, safeAreaInsetValues } from '@/utils'; - -const ViewTypes = { - COMPONENT_HEADER: 0, - FOOTER: 1, - HEADER: 2, - ROW: 3, - SWAPPED_ROW: 4, -}; - -const Wrapper = styled.View({ - flex: 1, - overflow: 'hidden', - width: '100%', -}); - -const hasRowChanged = (r1, r2) => { - const props1 = r1?.header?.props; - const props2 = r2?.header?.props; - if ( - r1.hash === '_header' && - (props1?.accountAddress !== props2?.accountAddress || - props1?.accountName !== props2?.accountName || - props1?.accountColor !== props2?.accountColor) - ) { - return true; - } - - const r1Key = r1?.hash ?? r1?.displayDetails?.timestampInMs ?? ''; - const r2Key = r2?.hash ?? r2?.displayDetails?.timestampInMs ?? ''; - - return ( - r1Key !== r2Key || - r1?.contact !== r2?.contact || - r1?.native?.symbol !== r2?.native?.symbol || - r1?.pending !== r2?.pending - ); -}; - -export default class RecyclerActivityList extends PureComponent { - constructor(props) { - super(props); - - this.state = { - dataProvider: new DataProvider(hasRowChanged, this.getStableId), - headersIndices: [], - swappedIndices: [], - }; - - this.layoutProvider = new LayoutProvider( - index => { - if (index === 0) { - return props.showcase - ? ViewTypes.SHOWCASE_HEADER - : ViewTypes.COMPONENT_HEADER; - } - - if (this.state.headersIndices.includes(index)) { - return ViewTypes.HEADER; - } - - if (this.state.headersIndices.includes(index + 1)) { - return ViewTypes.FOOTER; - } - - if (this.state.swappedIndices.includes(index)) { - return ViewTypes.SWAPPED_ROW; - } - - return ViewTypes.ROW; - }, - (type, dim) => { - // This values has been hardcoded for omitting imports' cycle - dim.width = deviceUtils.dimensions.width; - if (type === ViewTypes.ROW) { - dim.height = 70; - } else if (type === ViewTypes.SWAPPED_ROW) { - dim.height = 70; - } else if (type === ViewTypes.SHOWCASE_HEADER) { - dim.height = 400; - } else if (type === ViewTypes.FOOTER) { - dim.height = 19; - } else if (type === ViewTypes.HEADER) { - dim.height = 39; - } else { - // this handles the inital list height offset atm - dim.height = 20; - } - } - ); - this.layoutProvider.shouldRefreshWithAnchoring = false; - } - - static getDerivedStateFromProps(props, state) { - const headersIndices = []; - const swappedIndices = []; - let index = 1; - const items = props.sections.reduce( - (ctx, section) => { - section.data.forEach(asset => { - if (asset.status === TransactionStatusTypes.swapped) { - swappedIndices.push(index); - } - index++; - }); - index = index + 2; - headersIndices.push(ctx.length); - return ctx - .concat([ - { - hash: section.title, - title: section.title, - }, - ]) - .concat(section.data) - .concat([{ hash: `${section.title}_end` }]); // footer - }, - [{ hash: '_header', header: props.header }] - ); // header - if (items.length > 1) { - items.pop(); // remove last footer - } - return { - dataProvider: state.dataProvider.cloneWithRows(items), - headersIndices, - swappedIndices, - }; - } - - getStableId = index => { - const row = this.state?.dataProvider?._data?.[index]; - return buildTransactionUniqueIdentifier(row); - }; - - handleListRef = ref => { - this.rlv = ref; - }; - - rowRenderer = (type, data) => { - if (type === ViewTypes.COMPONENT_HEADER) { - const header = ( - - ); - return null; - } - if (type === ViewTypes.HEADER) return ; - if (type === ViewTypes.FOOTER) return ; - - if (!data) return null; - if (!data.hash) return ; - if (!data.symbol && data.dappName) - return ; - return ; - }; - - render() { - return ( - - - - ); - } -} diff --git a/src/components/asset-list/AssetListHeader.js b/src/components/asset-list/AssetListHeader.js index 6c3f7d4a089..feac53e6ad5 100644 --- a/src/components/asset-list/AssetListHeader.js +++ b/src/components/asset-list/AssetListHeader.js @@ -1,9 +1,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { IS_TESTING } from 'react-native-dotenv'; import LinearGradient from 'react-native-linear-gradient'; -import { useSelector } from 'react-redux'; import { abbreviations, magicMemo, measureText } from '../../utils'; -import { DividerSize } from '../Divider'; import { ButtonPressAnimation } from '../animations'; import { Icon } from '../icons'; import { Centered, Row } from '../layout'; @@ -14,6 +12,7 @@ import { StickyHeader } from './RecyclerAssetList2/core/StickyHeaders'; import { useAccountProfile, useDimensions } from '@/hooks'; import { useNavigation } from '@/navigation'; import Routes from '@/navigation/routesNames'; +import { useUserAssetCount } from '@/resources/assets/useUserAssetCount'; import styled from '@/styled-thing'; import { fonts, position } from '@/styles'; import { useTheme } from '@/theme'; @@ -118,7 +117,7 @@ const AssetListHeader = ({ const { width: deviceWidth } = useDimensions(); const { accountName } = useAccountProfile(); const { navigate } = useNavigation(); - const isLoadingAssets = useSelector(state => state.data.isLoadingAssets); + const { isLoading: isLoadingUserAssets } = useUserAssetCount(); const onChangeWallet = useCallback(() => { navigate(Routes.CHANGE_WALLET_SHEET); @@ -126,7 +125,7 @@ const AssetListHeader = ({ const [textWidth, setTextWidth] = useState(0); - const amountWidth = isLoadingAssets + const amountWidth = isLoadingUserAssets ? placeholderWidth + 16 : totalValue?.length * 15; const maxWidth = deviceWidth - dropdownArrowWidth - amountWidth - 32; @@ -163,7 +162,7 @@ const AssetListHeader = ({ /> )} - {isLoadingAssets && + {isLoadingUserAssets && title !== lang.t(lang.l.account.tab_collectibles) ? ( @@ -180,7 +179,7 @@ const AssetListHeader = ({ contextMenuOptions, deviceWidth, isCoinListEdited, - isLoadingAssets, + isLoadingUserAssets, maxWidth, onChangeWallet, props, diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx index 7967ba91228..edaa91a98bf 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastBalanceCoinRow.tsx @@ -140,7 +140,7 @@ const MemoizedBalanceCoinRow = React.memo( - ) : shouldRenderLocalCoinIconImage ? ( - - - ) : shouldRenderContract ? ( ) : ( )} - {assetType && } + {network && } ); }); @@ -177,10 +142,6 @@ const sx = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - reactCoinIconImage: { - height: '100%', - width: '100%', - }, withShadow: { elevation: 6, shadowOffset: { diff --git a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx index 0f04e6c0efe..38170e4ace3 100644 --- a/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/FastComponents/FastCurrencySelectionRow.tsx @@ -14,7 +14,7 @@ import FastCoinIcon from './FastCoinIcon'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; import { Text } from '@/design-system'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers'; +import { Network } from '@/networks/types'; import { useAccountAsset } from '@/hooks'; import { colors, fonts, fontWithWidth, getFontSize } from '@/styles'; import { deviceUtils, ethereumUtils } from '@/utils'; @@ -151,7 +151,7 @@ export default React.memo(function FastCurrencySelectionRow({ React.ReactNode; }) { const { colors } = theme; - const imageUrl = getUrlForTrustIconFallback(address, assetType)!; + const imageUrl = getUrlForTrustIconFallback(address, network)!; const key = `${symbol}-${imageUrl}`; - const shouldShowImage = imagesCache[key] !== ImageState.NOT_FOUND; - const isLoaded = imagesCache[key] === ImageState.LOADED; + const [cacheStatus, setCacheStatus] = useState(imagesCache[key]); - // we store data inside the object outside the component - // so we can share it between component instances - // but we still want the component to pick up new changes - const forceUpdate = useForceUpdate(); + const shouldShowImage = cacheStatus !== ImageState.NOT_FOUND; + const isLoaded = cacheStatus === ImageState.LOADED; const onLoad = useCallback(() => { - if (imagesCache[key] === ImageState.LOADED) { + if (isLoaded) { return; } - imagesCache[key] = ImageState.LOADED; - forceUpdate(); - }, [key, forceUpdate]); + setCacheStatus(ImageState.LOADED); + }, [key, isLoaded]); + const onError = useCallback( // @ts-expect-error passed to an untyped JS component err => { @@ -58,15 +54,14 @@ export const FastFallbackCoinIconImage = React.memo( ? ImageState.NOT_FOUND : ImageState.ERROR; - if (imagesCache[key] === newError) { + if (cacheStatus === newError) { return; - } else { - imagesCache[key] = newError; } - forceUpdate(); + imagesCache[key] = newError; + setCacheStatus(newError); }, - [key, forceUpdate] + [cacheStatus, key] ); return ( diff --git a/src/components/asset-list/RecyclerAssetList2/core/RefreshControl.tsx b/src/components/asset-list/RecyclerAssetList2/core/RefreshControl.tsx index 839b895377b..685333e910e 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/RefreshControl.tsx +++ b/src/components/asset-list/RecyclerAssetList2/core/RefreshControl.tsx @@ -1,25 +1,19 @@ import React from 'react'; import { RefreshControl, RefreshControlProps } from 'react-native'; -import { useSelector } from 'react-redux'; import { useRefreshAccountData } from '@/hooks'; -import { AppState } from '@/redux/store'; import { useTheme } from '@/theme'; import { navbarHeight } from '@/components/navbar/Navbar'; export default function RefreshControlWrapped( props: Partial ) { - const isLoadingAssets = useSelector( - (state: AppState) => state.data.isLoadingAssets - ); const { refresh, isRefreshing } = useRefreshAccountData(); const { colors } = useTheme(); - const onRefresh = isLoadingAssets ? () => {} : refresh; return ( ); diff --git a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts index 91c8b0aafd8..e05d6632247 100644 --- a/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts +++ b/src/components/asset-list/RecyclerAssetList2/core/ViewTypes.ts @@ -52,6 +52,7 @@ export type CoinDividerExtraData = { export type AssetsHeaderExtraData = { type: CellType.PROFILE_STICKY_HEADER; value: string; + isLoadingUserAssets: boolean; }; export type CoinExtraData = { type: CellType.COIN; uniqueId: string }; export type NFTExtraData = { diff --git a/src/components/asset-list/RecyclerAssetList2/index.tsx b/src/components/asset-list/RecyclerAssetList2/index.tsx index 097f13b411e..07eb44cc75e 100644 --- a/src/components/asset-list/RecyclerAssetList2/index.tsx +++ b/src/components/asset-list/RecyclerAssetList2/index.tsx @@ -70,7 +70,10 @@ function RecyclerAssetList({ briefSectionsData={briefSectionsData} disablePullDownToRefresh={!!disablePullDownToRefresh} extendedState={extendedState} - scrollIndicatorInsets={{ bottom: 40, top: 40 }} + scrollIndicatorInsets={{ + bottom: insets.bottom + 14, + top: 132, + }} type={type} /> @@ -177,13 +180,12 @@ function NavbarOverlay({ actionTitle: lang.t('button.my_qr_code'), icon: { iconType: 'SYSTEM', iconValue: 'qrcode' }, }, - mostRecentWalletConnectors.length > 0 || activeWCV2Sessions.length > 0 - ? { - actionKey: 'connectedApps', - actionTitle: lang.t('wallet.connected_apps'), - icon: { iconType: 'SYSTEM', iconValue: 'app.badge.checkmark' }, - } - : null, + + { + actionKey: 'connectedApps', + actionTitle: lang.t('wallet.connected_apps'), + icon: { iconType: 'SYSTEM', iconValue: 'app.badge.checkmark' }, + }, ].filter(Boolean), ...(ios ? { menuTitle: '' } : {}), }), diff --git a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx index fcf5c3bfefc..0c2181606cd 100644 --- a/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx +++ b/src/components/asset-list/RecyclerAssetList2/profile-header/ProfileBalanceRow.tsx @@ -1,22 +1,22 @@ import * as React from 'react'; -import { useSelector } from 'react-redux'; import Skeleton, { FakeText } from '@/components/skeleton/Skeleton'; import { Box, Heading } from '@/design-system'; -import { AppState } from '@/redux/store'; export const ProfileBalanceRowHeight = 24; -export function ProfileBalanceRow({ totalValue }: { totalValue: string }) { - const isLoadingAssets = useSelector( - (state: AppState) => state.data.isLoadingAssets - ); - +export function ProfileBalanceRow({ + totalValue, + isLoadingUserAssets, +}: { + totalValue: string; + isLoadingUserAssets: boolean; +}) { const placeholderHeight = ProfileBalanceRowHeight; const placeholderWidth = 200; return ( <> - {isLoadingAssets ? ( + {isLoadingUserAssets ? ( { setCanRefresh(false); queryClient.invalidateQueries( nftOffersQueryKey({ - address: accountAddress, + walletAddress: accountAddress, }) ); }} diff --git a/src/components/change-wallet/AddressRow.tsx b/src/components/change-wallet/AddressRow.tsx index b05b9daf9ca..a5af90c724b 100644 --- a/src/components/change-wallet/AddressRow.tsx +++ b/src/components/change-wallet/AddressRow.tsx @@ -13,7 +13,6 @@ import { Icon } from '../icons'; import { Centered, Column, ColumnWithMargins, Row } from '../layout'; import { Text, TruncatedText } from '../text'; import ContextMenuButton from '@/components/native-context-menu/contextMenu'; -import ContextMenuAndroid from '@/components/native-context-menu/contextMenu.android'; import useExperimentalFlag, { NOTIFICATIONS } from '@/config/experimentalHooks'; import { removeFirstEmojiFromString, @@ -25,6 +24,7 @@ import { abbreviations, deviceUtils, profileUtils } from '@/utils'; import { EditWalletContextMenuActions } from '@/screens/ChangeWalletSheet'; import { toChecksumAddress } from '@/handlers/web3'; import { IS_IOS, IS_ANDROID } from '@/env'; +import { ContextMenu } from '../context-menu'; const maxAccountLabelWidth = deviceUtils.dimensions.width - 88; const NOOP = () => undefined; @@ -219,6 +219,25 @@ export default function AddressRow({ menuTitle: walletName, }; + const handleSelectActionMenuItem = useCallback( + (buttonIndex: number) => { + switch (buttonIndex) { + case 0: + contextMenuActions?.edit(walletId, address); + break; + case 1: + contextMenuActions?.notifications(walletName, address); + break; + case 2: + contextMenuActions?.remove(walletId, address); + break; + default: + break; + } + }, + [contextMenuActions, walletName, walletId, address] + ); + const handleSelectMenuItem = useCallback( // @ts-expect-error ContextMenu is an untyped JS component and can't type its onPress handler properly ({ nativeEvent: { actionKey } }) => { @@ -316,14 +335,15 @@ export default function AddressRow({ ) : ( - // @ts-expect-error js component - item.actionTitle)} isAnchoredToRight - onPressMenuItem={handleSelectMenuItem} + onPressActionSheet={handleSelectActionMenuItem} > - - + + + + ))} diff --git a/src/components/coin-icon/ChainBadgeSizeConfigs.ts b/src/components/coin-icon/ChainBadgeSizeConfigs.ts index a48eec40e48..8f60c4e38cd 100644 --- a/src/components/coin-icon/ChainBadgeSizeConfigs.ts +++ b/src/components/coin-icon/ChainBadgeSizeConfigs.ts @@ -19,6 +19,10 @@ export const ChainBadgeSizeConfigs = { containerSize: 30, iconSize: 14, }, + xtiny: { + containerSize: 26.4, + iconSize: 12, + }, } as const; export type ChainBadgeType = keyof typeof ChainBadgeSizeConfigs; diff --git a/src/components/coin-icon/ChainImage.tsx b/src/components/coin-icon/ChainImage.tsx new file mode 100644 index 00000000000..6e5e622543c --- /dev/null +++ b/src/components/coin-icon/ChainImage.tsx @@ -0,0 +1,52 @@ +import React, { useMemo } from 'react'; +import { Source } from 'react-native-fast-image'; + +import { IS_IOS } from '@/env'; +import { Network } from '@/helpers'; + +import ArbitrumBadge from '../../assets/badges/arbitrum.png'; +import BaseBadge from '../../assets/badges/base.png'; +import BscBadge from '../../assets/badges/bsc.png'; +import EthereumBadge from '../../assets/badges/ethereum.png'; +import OptimismBadge from '../../assets/badges/optimism.png'; +import PolygonBadge from '../../assets/badges/polygon.png'; +import ZoraBadge from '../../assets/badges/zora.png'; +import { ImgixImage } from '../images'; + +export function ChainImage({ + chain, + size = 20, +}: { + chain: Network | null | undefined; + size?: number; +}) { + const source = useMemo(() => { + let val = null; + if (chain === Network.arbitrum) { + val = IS_IOS ? { uri: 'arbitrum' } : ArbitrumBadge; + } else if (chain === Network.base) { + val = IS_IOS ? { uri: 'base' } : BaseBadge; + } else if (chain === Network.bsc) { + val = IS_IOS ? { uri: 'bsc' } : BscBadge; + } else if (chain === Network.mainnet) { + val = IS_IOS ? { uri: 'ethereum' } : EthereumBadge; + } else if (chain === Network.optimism) { + val = IS_IOS ? { uri: 'optimism' } : OptimismBadge; + } else if (chain === Network.polygon) { + val = IS_IOS ? { uri: 'polygon' } : PolygonBadge; + } else if (chain === Network.zora) { + val = IS_IOS ? { uri: 'zora' } : ZoraBadge; + } + return val as Source; + }, [chain]); + + if (!chain) return null; + + return ( + + ); +} diff --git a/src/components/coin-icon/CoinIcon.tsx b/src/components/coin-icon/CoinIcon.tsx index de467b9d627..ce787640f06 100644 --- a/src/components/coin-icon/CoinIcon.tsx +++ b/src/components/coin-icon/CoinIcon.tsx @@ -1,16 +1,15 @@ -import { isNil } from 'lodash'; import React, { useMemo } from 'react'; import { View, ViewProps } from 'react-native'; import ContractInteraction from '../../assets/contractInteraction.png'; import { useTheme } from '../../theme/ThemeContext'; import ChainBadge from './ChainBadge'; import { CoinIconFallback } from './CoinIconFallback'; -import { AssetTypes } from '@/entities'; +import { Network } from '@/networks/types'; import { useColorForAsset } from '@/hooks'; import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { - getTokenMetadata, + ethereumUtils, isETH, magicMemo, CoinIcon as ReactCoinIcon, @@ -59,7 +58,6 @@ const CoinIcon: React.FC = ({ }) => { const color = useColorForAsset({ address: mainnet_address || address, - type: mainnet_address ? AssetTypes.token : type, }); const { colors, isDarkMode } = useTheme(); const forceFallback = !isETH(mainnet_address || address); @@ -69,6 +67,12 @@ const CoinIcon: React.FC = ({ const theme = useTheme(); + const network = mainnet_address + ? Network.mainnet + : type + ? ethereumUtils.getNetworkFromType(type) + : Network.mainnet; + return ( {isNotContractInteraction ? ( @@ -85,8 +89,7 @@ const CoinIcon: React.FC = ({ } size={size} symbol={symbol} - type={mainnet_address ? AssetTypes.token : type} - assetType={mainnet_address ? AssetTypes.token : type} + network={network} theme={theme} /> ) : ( diff --git a/src/components/coin-icon/CoinIconFallback.js b/src/components/coin-icon/CoinIconFallback.js index be9fd6c9665..4bc8dea489c 100644 --- a/src/components/coin-icon/CoinIconFallback.js +++ b/src/components/coin-icon/CoinIconFallback.js @@ -31,8 +31,8 @@ const fallbackIconStyle = size => { export const CoinIconFallback = fallbackProps => { const { address, - assetType, height, + network, symbol, shadowColor, theme, @@ -41,7 +41,7 @@ export const CoinIconFallback = fallbackProps => { } = fallbackProps; const { colors } = theme; - const imageUrl = getUrlForTrustIconFallback(address, assetType); + const imageUrl = getUrlForTrustIconFallback(address, network); const key = `${symbol}-${imageUrl}`; @@ -50,7 +50,6 @@ export const CoinIconFallback = fallbackProps => { const fallbackIconColor = useColorForAsset({ address, - assetType, }); // we store data inside the object outside the component diff --git a/src/components/coin-row/BalanceCoinRow.js b/src/components/coin-row/BalanceCoinRow.js deleted file mode 100644 index d565b7ea0bb..00000000000 --- a/src/components/coin-row/BalanceCoinRow.js +++ /dev/null @@ -1,191 +0,0 @@ -import React, { Fragment, useCallback } from 'react'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; -import { View } from 'react-primitives'; -import useCoinListEditOptions from '../../hooks/useCoinListEditOptions'; -import { useTheme } from '../../theme/ThemeContext'; -import { ButtonPressAnimation } from '../animations'; -import { FlexItem, Row } from '../layout'; -import BalanceText from './BalanceText'; -import BottomRowText from './BottomRowText'; -import CoinCheckButton from './CoinCheckButton'; -import CoinName from './CoinName'; -import CoinRow from './CoinRow'; -import { useIsCoinListEditedSharedValue } from '@/helpers/SharedValuesContext'; -import { buildAssetUniqueIdentifier } from '@/helpers/assets'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; - -const editTranslateOffset = 37; - -const formatPercentageString = percentString => - percentString ? percentString.split('-').join('- ') : '-'; - -const BalanceCoinRowCoinCheckButton = styled(CoinCheckButton).attrs({ - left: 9.5, -})({ - top: 9, -}); - -const PercentageText = styled(BottomRowText).attrs({ - align: 'right', -})({ - color: ({ isPositive, theme: { colors } }) => - isPositive ? colors.green : colors.alpha(colors.blueGreyDark, 0.5), -}); - -const BottomRowContainer = ios - ? Fragment - : styled(Row).attrs({ marginBottom: 10, marginTop: -10 })({}); - -const TopRowContainer = ios - ? Fragment - : styled(Row).attrs({ - align: 'flex-start', - justify: 'flex-start', - marginTop: 0, - })({}); - -const PriceContainer = ios - ? View - : styled(View)({ - marginBottom: 3, - marginTop: -3, - }); - -const BottomRow = ({ balance, native }) => { - const { colors } = useTheme(); - const percentChange = native?.change; - const percentageChangeDisplay = formatPercentageString(percentChange); - - const isPositive = percentChange && percentageChangeDisplay.charAt(0) !== '-'; - - return ( - - - - {balance?.display ?? ''} - - - - - {percentageChangeDisplay} - - - - ); -}; - -const TopRow = ({ name, native, nativeCurrencySymbol }) => { - const nativeDisplay = native?.balance?.display; - const { colors } = useTheme(); - - return ( - - - {name} - - - - {nativeDisplay || `${nativeCurrencySymbol}0.00`} - - - - ); -}; - -const arePropsEqual = (prev, next) => { - const itemIdentifier = buildAssetUniqueIdentifier(prev.item); - const nextItemIdentifier = buildAssetUniqueIdentifier(next.item); - const isSameItem = itemIdentifier === nextItemIdentifier; - return isSameItem; -}; - -const BalanceCoinRow = ({ - containerStyles = null, - isFirstCoinRow = false, - item, - ...props -}) => { - const { toggleSelectedCoin } = useCoinListEditOptions(); - const isCoinListEditedSharedValue = useIsCoinListEditedSharedValue(); - const { navigate } = useNavigation(); - - const handleEditModePress = useCallback(() => { - toggleSelectedCoin(item.uniqueId); - }, [item.uniqueId, toggleSelectedCoin]); - - const handlePress = useCallback(() => { - if (isCoinListEditedSharedValue.value) { - handleEditModePress(); - } else { - navigate(Routes.EXPANDED_ASSET_SHEET, { - asset: item, - type: 'token', - }); - } - }, [isCoinListEditedSharedValue, handleEditModePress, navigate, item]); - - const paddingStyle = useAnimatedStyle( - () => ({ - paddingLeft: - (isCoinListEditedSharedValue.value ? 1 : 0) * editTranslateOffset, - position: 'absolute', - width: '100%', - }), - [] - ); - - const marginStyle = useAnimatedStyle( - () => ({ - marginLeft: - -editTranslateOffset * - 1.5 * - (isCoinListEditedSharedValue.value ? 0 : 1), - position: 'absolute', - }), - [] - ); - - const { hiddenCoinsObj, pinnedCoinsObj } = useCoinListEditOptions(); - const isPinned = pinnedCoinsObj[item.uniqueId]; - const isHidden = hiddenCoinsObj[item.uniqueId]; - return ( - - - - - - - - - - - - - ); -}; - -export default React.memo(BalanceCoinRow, arePropsEqual); diff --git a/src/components/coin-row/BalanceText.js b/src/components/coin-row/BalanceText.js deleted file mode 100644 index 582aa792e7b..00000000000 --- a/src/components/coin-row/BalanceText.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Text } from '../text'; -import styled from '@/styled-thing'; - -const BalanceText = styled(Text).attrs(({ color, theme: { colors } }) => ({ - align: 'right', - color: color || colors.dark, - size: 'lmedium', - weight: 'medium', -}))({}); - -export default BalanceText; diff --git a/src/components/coin-row/ContractInteractionCoinRow.js b/src/components/coin-row/ContractInteractionCoinRow.js deleted file mode 100644 index a799fb08172..00000000000 --- a/src/components/coin-row/ContractInteractionCoinRow.js +++ /dev/null @@ -1,50 +0,0 @@ -import lang from 'i18n-js'; -import React, { useCallback } from 'react'; -import { ButtonPressAnimation } from '../animations'; -import { CoinIconSize, RequestVendorLogoIcon } from '../coin-icon'; -import CoinName from './CoinName'; -import CoinRow from './CoinRow'; -import TransactionStatusBadge from './TransactionStatusBadge'; -import styled from '@/styled-thing'; -import { ethereumUtils, showActionSheetWithOptions } from '@/utils'; - -const BottomRow = ({ dappName }) => {dappName}; - -const ContractInteractionVenderLogoIcon = styled(RequestVendorLogoIcon).attrs({ - borderRadius: CoinIconSize, -})({}); - -export default function ContractInteractionCoinRow({ - item: { hash, ...item }, - ...props -}) { - const handlePressTransaction = useCallback(() => { - if (!hash) return; - showActionSheetWithOptions( - { - cancelButtonIndex: 1, - options: [ - lang.t('exchange.coin_row.view_on_etherscan'), - lang.t('button.cancel'), - ], - }, - buttonIndex => { - if (buttonIndex === 0) { - ethereumUtils.openTransactionInBlockExplorer(hash); - } - } - ); - }, [hash]); - - return ( - - - - ); -} diff --git a/src/components/coin-row/FastTransactionCoinRow.tsx b/src/components/coin-row/FastTransactionCoinRow.tsx index 19511125b42..db7d0343e1f 100644 --- a/src/components/coin-row/FastTransactionCoinRow.tsx +++ b/src/components/coin-row/FastTransactionCoinRow.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { StyleSheet, View } from 'react-native'; import { ButtonPressAnimation } from '../animations'; import FastCoinIcon from '../asset-list/RecyclerAssetList2/FastComponents/FastCoinIcon'; @@ -154,7 +154,7 @@ export default React.memo(function TransactionCoinRow({ ) : ( { - const { colors } = useTheme(); - const isFailed = status === TransactionStatusTypes.failed; - const isReceived = - status === TransactionStatusTypes.received || - status === TransactionStatusTypes.purchased; - const isSent = status === TransactionStatusTypes.sent; - - const isOutgoingSwap = status === TransactionStatusTypes.swapped; - const isIncomingSwap = - status === TransactionStatusTypes.received && - type === TransactionTypes.trade; - - let coinNameColor = colors.dark; - if (isOutgoingSwap) coinNameColor = colors.alpha(colors.blueGreyDark, 0.5); - - let balanceTextColor = colors.alpha(colors.blueGreyDark, 0.5); - if (isReceived) balanceTextColor = colors.green; - if (isSent) balanceTextColor = colors.dark; - if (isIncomingSwap) balanceTextColor = colors.swapPurple; - if (isOutgoingSwap) balanceTextColor = colors.dark; - - const nativeDisplay = native?.display; - const balanceText = nativeDisplay - ? compact([isFailed || isSent ? '-' : null, nativeDisplay]).join(' ') - : ''; - - return ( - - - {description} - - - {balanceText} - - - ); -}; - -const TopRow = ({ balance, pending, status, title }) => ( - - - - {balance?.display ?? ''} - - -); - -export default function TransactionCoinRow({ item, ...props }) { - const { contact } = item; - const { accountAddress } = useAccountSettings(); - const { navigate } = useNavigation(); - - const onPressTransaction = useCallback(async () => { - const { hash, from, minedAt, pending, to, status, type, network } = item; - - const date = getHumanReadableDate(minedAt); - const isSent = - status === TransactionStatusTypes.sending || - status === TransactionStatusTypes.sent; - const showContactInfo = hasAddableContact(status, type); - - const isOutgoing = from?.toLowerCase() === accountAddress.toLowerCase(); - const canBeResubmitted = isOutgoing && !minedAt; - const canBeCancelled = - canBeResubmitted && status !== TransactionStatusTypes.cancelling; - - const headerInfo = { - address: '', - divider: isSent - ? lang.t('exchange.coin_row.to_divider') - : lang.t('exchange.coin_row.from_divider'), - type: status.charAt(0).toUpperCase() + status.slice(1), - }; - - const contactAddress = isSent ? to : from; - let contactColor = 0; - - if (contact) { - headerInfo.address = contact.nickname; - contactColor = contact.color; - } else { - headerInfo.address = isValidDomainFormat(contactAddress) - ? contactAddress - : abbreviations.address(contactAddress, 4, 10); - contactColor = getRandomColor(); - } - - const blockExplorerAction = lang.t('exchange.coin_row.view_on', { - blockExplorerName: startCase(ethereumUtils.getBlockExplorer(network)), - }); - if (hash) { - const buttons = [ - ...(canBeResubmitted ? [TransactionActions.speedUp] : []), - ...(canBeCancelled ? [TransactionActions.cancel] : []), - blockExplorerAction, - ...(ios ? [TransactionActions.close] : []), - ]; - if (showContactInfo) { - buttons.unshift( - contact - ? TransactionActions.viewContact - : TransactionActions.addToContacts - ); - } - - showActionSheetWithOptions( - { - cancelButtonIndex: buttons.length - 1, - options: buttons, - title: pending - ? `${headerInfo.type}${ - showContactInfo - ? ' ' + headerInfo.divider + ' ' + headerInfo.address - : '' - }` - : showContactInfo - ? `${headerInfo.type} ${date} ${headerInfo.divider} ${headerInfo.address}` - : `${headerInfo.type} ${date}`, - }, - buttonIndex => { - const action = buttons[buttonIndex]; - switch (action) { - case TransactionActions.viewContact: - case TransactionActions.addToContacts: - navigate(Routes.MODAL_SCREEN, { - address: contactAddress, - asset: item, - color: contactColor, - contact, - type: 'contact_profile', - }); - break; - case TransactionActions.speedUp: - navigate(Routes.SPEED_UP_AND_CANCEL_SHEET, { - tx: item, - type: 'speed_up', - }); - break; - case TransactionActions.cancel: - navigate(Routes.SPEED_UP_AND_CANCEL_SHEET, { - tx: item, - type: 'cancel', - }); - break; - case TransactionActions.close: - return; - case blockExplorerAction: - ethereumUtils.openTransactionInBlockExplorer(hash, network); - break; - default: { - return; - } - } - } - ); - } - }, [accountAddress, contact, item, navigate]); - - const mainnetAddress = useSelector( - state => - state.data.accountAssetsData?.[`${item.address}_${item.network}`] - ?.mainnet_address - ); - - return ( - - - - ); -} diff --git a/src/components/coin-row/TransactionStatusBadge.js b/src/components/coin-row/TransactionStatusBadge.js deleted file mode 100644 index dd3e9569647..00000000000 --- a/src/components/coin-row/TransactionStatusBadge.js +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react'; -import Spinner from '../Spinner'; -import { Icon } from '../icons'; -import { Row } from '../layout'; -import { Text } from '../text'; -import { TransactionStatusTypes } from '@/entities'; -import { position } from '@/styles'; -import { magicMemo } from '@/utils'; - -const StatusProps = { - [TransactionStatusTypes.approved]: { - marginRight: 4, - name: 'dot', - }, - [TransactionStatusTypes.cancelled]: { - marginRight: 4, - }, - [TransactionStatusTypes.cancelling]: { - marginRight: 4, - }, - [TransactionStatusTypes.deposited]: { - name: 'sunflower', - style: { fontSize: 11, left: -1.3, marginBottom: 1.5, marginRight: 1 }, - }, - [TransactionStatusTypes.depositing]: { - marginRight: 4, - }, - [TransactionStatusTypes.approving]: { - marginRight: 4, - }, - [TransactionStatusTypes.swapping]: { - marginRight: 4, - }, - [TransactionStatusTypes.speeding_up]: { - marginRight: 4, - }, - [TransactionStatusTypes.dropped]: { - marginRight: 4, - name: 'closeCircled', - style: position.maxSizeAsObject(12), - }, - [TransactionStatusTypes.failed]: { - marginRight: 4, - name: 'closeCircled', - style: position.maxSizeAsObject(12), - }, - [TransactionStatusTypes.purchased]: { - marginRight: 2, - name: 'arrow', - }, - [TransactionStatusTypes.purchasing]: { - marginRight: 4, - }, - [TransactionStatusTypes.received]: { - marginRight: 2, - name: 'arrow', - }, - [TransactionStatusTypes.self]: { - marginRight: 4, - name: 'dot', - }, - [TransactionStatusTypes.sending]: { - marginRight: 4, - }, - [TransactionStatusTypes.sent]: { - marginRight: 3, - name: 'sendSmall', - }, - [TransactionStatusTypes.swapped]: { - marginRight: 3, - name: 'swap', - small: true, - style: position.maxSizeAsObject(12), - }, - [TransactionStatusTypes.contract_interaction]: { - name: 'robot', - style: { fontSize: 11, left: -1.3, marginBottom: 1.5, marginRight: 1 }, - }, - [TransactionStatusTypes.swapping]: { - marginRight: 4, - }, - [TransactionStatusTypes.withdrawing]: { - marginRight: 4, - }, - [TransactionStatusTypes.withdrew]: { - name: 'sunflower', - style: { fontSize: 11, left: -1.3, marginBottom: 1.5, marginRight: 1 }, - }, -}; - -const TransactionStatusBadge = ({ pending, status, style, title }) => { - const { colors } = useTheme(); - const isSwapping = status === TransactionStatusTypes.swapping; - - let statusColor = colors.alpha(colors.blueGreyDark, 0.7); - if (pending) { - if (isSwapping) { - statusColor = colors.swapPurple; - } else { - statusColor = colors.appleBlue; - } - } else if (status === TransactionStatusTypes.swapped) { - statusColor = colors.swapPurple; - } - - return ( - - {pending && ( - - )} - {status && Object.keys(StatusProps).includes(status) && ( - - )} - - {title} - - - ); -}; - -export default magicMemo(TransactionStatusBadge, [ - 'pending', - 'status', - 'title', -]); diff --git a/src/components/coin-row/index.js b/src/components/coin-row/index.js index 2ee887fca10..86680ae6c2f 100644 --- a/src/components/coin-row/index.js +++ b/src/components/coin-row/index.js @@ -1,15 +1,12 @@ -export { default as BalanceCoinRow } from './BalanceCoinRow'; export { default as BottomRowText } from './BottomRowText'; export { default as CoinRow, CoinRowHeight } from './CoinRow'; export { default as CoinRowAddButton } from './CoinRowAddButton'; export { default as CoinRowDetailsIcon } from './CoinRowDetailsIcon'; export { default as CoinRowFavoriteButton } from './CoinRowFavoriteButton'; export { default as CollectiblesSendRow } from './CollectiblesSendRow'; -export { default as ContractInteractionCoinRow } from './ContractInteractionCoinRow'; export { default as ListCoinRow } from './ListCoinRow'; export { default as RequestCoinRow } from './RequestCoinRow'; export { default as SendCoinRow } from './SendCoinRow'; export { default as SendSavingsCoinRow } from './SendSavingsCoinRow'; export { default as FastTransactionCoinRow } from './FastTransactionCoinRow'; -export { default as TransactionCoinRow } from './TransactionCoinRow'; export { default as UnderlyingAssetCoinRow } from './UnderlyingAssetCoinRow'; diff --git a/src/components/contacts/ContactAvatar.js b/src/components/contacts/ContactAvatar.js index 3f29234abee..7c7c33b77eb 100644 --- a/src/components/contacts/ContactAvatar.js +++ b/src/components/contacts/ContactAvatar.js @@ -10,8 +10,7 @@ import ShadowStack from '@/react-native-shadow-stack'; import { IS_ANDROID } from '@/env'; const buildShadows = (color, size, darkMode, colors) => { - // TODO: remove `legacySmall` size once rainbow home screen revamp is released - if (size === 'small' || size === 'legacySmall') { + if (size === 'small') { return [ [0, 3, 5, colors.shadow, 0.14], [ @@ -46,6 +45,17 @@ const buildShadows = (color, size, darkMode, colors) => { 0.4, ], ]; + } else if (size === 'signing') { + return [ + [ + 0, + 4, + 12, + darkMode ? colors.shadow : colors.avatarBackgrounds[color] || color, + darkMode ? 0.16 : 0.2, + ], + [0, 2, 6, colors.trueBlack, 0.02], + ]; } else { return sizeConfigs(colors)[size]['shadow']; } @@ -68,6 +78,14 @@ const sizeConfigs = colors => ({ ], textSize: 28, }, + sim: { + dimensions: 44, + shadow: [ + [0, 4, 6, colors.shadow, 0.04], + [0, 1, 3, colors.shadow, 0.08], + ], + textSize: 'larger', + }, medium: { dimensions: 40, shadow: [ @@ -76,6 +94,10 @@ const sizeConfigs = colors => ({ ], textSize: 'larger', }, + signing: { + dimensions: 44, + textSize: 25, + }, small: { dimensions: 36, textSize: 'large', @@ -85,11 +107,6 @@ const sizeConfigs = colors => ({ textSize: 'large', shadow: [[0, 0, 0, colors.shadow, 0]], }, - // TODO: remove `legacySmall` size once rainbow home screen revamp is released - legacySmall: { - dimensions: 34, - textSize: 'large', - }, smaller: { dimensions: 20, textSize: 'micro', @@ -127,7 +144,7 @@ const ContactAvatar = ({ color, size = 'medium', value, ...props }) => { typeof color === 'number' ? // sometimes the color is gonna be missing so we fallback to white // otherwise there will be only shadows without the the placeholder "circle" - colors.avatarBackgrounds[color] ?? 'white' + colors.avatarBackgrounds[color] ?? colors.white : color; return ( diff --git a/src/components/contacts/ImageAvatar.js b/src/components/contacts/ImageAvatar.js index bafb5288c12..e82ae065935 100644 --- a/src/components/contacts/ImageAvatar.js +++ b/src/components/contacts/ImageAvatar.js @@ -5,13 +5,14 @@ import styled from '@/styled-thing'; import { borders } from '@/styles'; import ShadowStack from '@/react-native-shadow-stack'; import { IS_ANDROID } from '@/env'; +import { useAccountAccentColor } from '@/hooks'; const buildSmallShadows = (color, colors) => [ [0, 3, 5, colors.shadow, 0.14], [0, 6, 10, colors.avatarBackgrounds[color] || color, 0.2], ]; -const sizeConfigs = (colors, isDarkMode) => ({ +const sizeConfigs = (accentColor, colors, isDarkMode) => ({ header: { dimensions: 34, textSize: 'large', @@ -37,6 +38,20 @@ const sizeConfigs = (colors, isDarkMode) => ({ shadow: [[0, 4, 12, colors.shadow, isDarkMode ? 0.3 : 0.15]], textSize: 'larger', }, + signing: { + dimensions: 44, + shadow: [ + [ + 0, + 4, + 12, + !isDarkMode && accentColor ? accentColor : colors.shadow, + isDarkMode ? 0.16 : 0.2, + ], + [0, 2, 6, colors.trueBlack, 0.02], + ], + textSize: 25, + }, small: { dimensions: 30, shadow: [[0, 3, 9, colors.shadow, 0.1]], @@ -79,10 +94,11 @@ const ImageAvatar = ({ onLoad = undefined, ...props }) => { + const { accentColor } = useAccountAccentColor(); const { colors, isDarkMode } = useTheme(); const { dimensions, shadow } = useMemo( - () => sizeConfigs(colors, isDarkMode)[size], - [colors, isDarkMode, size] + () => sizeConfigs(accentColor, colors, isDarkMode)[size], + [accentColor, colors, isDarkMode, size] ); const shadows = useMemo( diff --git a/src/components/exchange/ExchangeTokenRow.tsx b/src/components/exchange/ExchangeTokenRow.tsx index b5bb1346fd2..b61bf55cc3a 100644 --- a/src/components/exchange/ExchangeTokenRow.tsx +++ b/src/components/exchange/ExchangeTokenRow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import isEqual from 'react-fast-compare'; import { Box, Column, Columns, Inline, Stack, Text } from '@/design-system'; import { isNativeAsset } from '@/handlers/assets'; -import { Network } from '@/helpers'; +import { Network } from '@/networks/types'; import { useAccountAsset, useDimensions } from '@/hooks'; import { ethereumUtils } from '@/utils'; import FastCoinIcon from '../asset-list/RecyclerAssetList2/FastComponents/FastCoinIcon'; @@ -69,7 +69,7 @@ export default React.memo(function ExchangeTokenRow({ (null); const networkMenuItems = useMemo(() => { - return sortNetworks(ethereumUtils.getNetworkFromChainId(currentChainId)) + return sortNetworks() .filter(network => network.features.swaps) .map(network => ({ chainId: network.id, @@ -34,7 +34,7 @@ const NetworkSwitcherv2 = ({ type: network.value !== Network.mainnet ? network.value : AssetType.token, })); - }, [currentChainId]); + }, []); const radialGradientProps = (network: Network) => { return { diff --git a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx index 6d0f164e254..bd9ce774e4d 100644 --- a/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx +++ b/src/components/expanded-state/unique-token/NFTBriefTokenInfoRow.tsx @@ -14,6 +14,8 @@ import { ethereumUtils } from '@/utils'; import { useNFTListing } from '@/resources/nfts'; import { UniqueAsset } from '@/entities'; import { fetchReservoirNFTFloorPrice } from '@/resources/nfts/utils'; +import { handleReviewPromptAction } from '@/utils/reviewAlert'; +import { ReviewPromptAction } from '@/storage/schema'; const NONE = 'None'; @@ -30,6 +32,7 @@ export default function NFTBriefTokenInfoRow({ }: { asset: UniqueAsset; }) { + const [hasDispatchedAction, setHasDispatchedAction] = useState(false); const { colors } = useTheme(); const { navigate } = useNavigation(); @@ -73,10 +76,18 @@ export default function NFTBriefTokenInfoRow({ ); const [showFloorInEth, setShowFloorInEth] = useState(true); - const toggleFloorDisplayCurrency = useCallback( - () => setShowFloorInEth(!showFloorInEth), - [showFloorInEth, setShowFloorInEth] - ); + const toggleFloorDisplayCurrency = useCallback(() => { + if (!hasDispatchedAction) { + handleReviewPromptAction(ReviewPromptAction.NftFloorPriceVisit); + setHasDispatchedAction(true); + } + setShowFloorInEth(!showFloorInEth); + }, [ + showFloorInEth, + setShowFloorInEth, + hasDispatchedAction, + setHasDispatchedAction, + ]); const handlePressCollectionFloor = useCallback(() => { navigate(Routes.EXPLAIN_SHEET, { diff --git a/src/components/floating-emojis/CopyFloatingEmojis.js b/src/components/floating-emojis/CopyFloatingEmojis.js index 2015d17c409..5f8c4be3325 100644 --- a/src/components/floating-emojis/CopyFloatingEmojis.js +++ b/src/components/floating-emojis/CopyFloatingEmojis.js @@ -30,7 +30,9 @@ const CopyFloatingEmojis = ({ onPress?.(textToCopy); if (!disabled) { onNewEmoji(); - setClipboard(textToCopy); + if (textToCopy) { + setClipboard(textToCopy); + } } }} radiusAndroid={24} diff --git a/src/components/floating-emojis/FloatingEmojis.js b/src/components/floating-emojis/FloatingEmojis.js index ad71a1687f3..9fca2c356f9 100644 --- a/src/components/floating-emojis/FloatingEmojis.js +++ b/src/components/floating-emojis/FloatingEmojis.js @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Animated, View } from 'react-native'; import FloatingEmoji from './FloatingEmoji'; +import GravityEmoji from './GravityEmoji'; import { useTimeout } from '@/hooks'; import { position } from '@/styles'; @@ -19,6 +20,7 @@ const FloatingEmojis = ({ duration, emojis, fadeOut, + gravityEnabled, marginTop, opacity, opacityThreshold, @@ -88,26 +90,39 @@ const FloatingEmojis = ({ ...position.coverAsObject, }} > - {floatingEmojis.map(({ emojiToRender, x, y }, index) => ( - - ))} + {gravityEnabled + ? floatingEmojis.map(({ emojiToRender, x, y }, index) => ( + + )) + : floatingEmojis.map(({ emojiToRender, x, y }, index) => ( + + ))} ); @@ -123,6 +138,7 @@ FloatingEmojis.propTypes = { duration: PropTypes.number, emojis: PropTypes.arrayOf(PropTypes.string).isRequired, fadeOut: PropTypes.bool, + gravityEnabled: PropTypes.bool, marginTop: PropTypes.number, opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), opacityThreshold: PropTypes.number, diff --git a/src/components/floating-emojis/FloatingEmojisTapHandler.js b/src/components/floating-emojis/FloatingEmojisTapHandler.js index 5eebe0edd67..88c4e10abae 100644 --- a/src/components/floating-emojis/FloatingEmojisTapHandler.js +++ b/src/components/floating-emojis/FloatingEmojisTapHandler.js @@ -8,16 +8,17 @@ export default function FloatingEmojisTapHandler({ disabled = false, onNewEmoji, onPress, + yOffset, ...props }) { const handleTap = useCallback( ({ nativeEvent: { state, x, y } }) => { if (state === State.ACTIVE) { - onNewEmoji?.(x, y); + onNewEmoji?.(x, y + (yOffset || 0)); onPress?.(); } }, - [onNewEmoji, onPress] + [onNewEmoji, onPress, yOffset] ); return ( diff --git a/src/components/floating-emojis/FloatingEmojisTapper.js b/src/components/floating-emojis/FloatingEmojisTapper.js index 0ced7cb5d29..80ff0acbac4 100644 --- a/src/components/floating-emojis/FloatingEmojisTapper.js +++ b/src/components/floating-emojis/FloatingEmojisTapper.js @@ -9,6 +9,7 @@ export default function FloatingEmojisTapper({ disabled, onPress, radiusAndroid, + yOffset, ...props }) { return ( @@ -24,6 +25,7 @@ export default function FloatingEmojisTapper({ disabled={disabled} onNewEmoji={onNewEmoji} onPress={onPress} + yOffset={yOffset} > { + const animation = useSharedValue(0); + + const getRandomNumber = (spread = 180) => + Math.random() * (spread - -spread) + -spread; + + const randomSpin = getRandomNumber(1080); + const randomDistance = Math.random() * 200 + 300; + + const duration = 2000; + const timeDilation = 1000; + + // 👾 GRAVITY SIMULATION 👾 // + const g = 9.81; + const scaledGravity = g * timeDilation * timeDilation; + + // Determine initial trajectory angle + const verticalBias = Math.random(); + let theta: number; + + if (verticalBias < 0.9) { + // 90% odds to move upwards + theta = (5 * Math.PI) / 3 + (verticalBias - 0.9) * ((2 * Math.PI) / 3); + } else { + // 10% odds to move downwards + theta = Math.PI / 3 + (verticalBias * 10 - 1) * ((4 * Math.PI) / 3); + } + + // Determine initial velocities + const xBoost = 3; + const yBoost = 9; + const v0x = xBoost * randomDistance * Math.cos(theta) * timeDilation; + const v0y = + (verticalBias < 0.9 ? yBoost : 0.1) * + randomDistance * + Math.sin(theta) * + timeDilation; + + useLayoutEffect(() => { + animation.value = withTiming(2, { + duration, + easing: Easing.linear, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const animatedStyle = useAnimatedStyle(() => { + const progress = interpolate(animation.value, [0, 1], [0, distance]); + + const scale = 0.5; + const rotate = + interpolate(progress, [0, distance], [0, randomSpin]) + 'deg'; + + const t = animation.value; + const translateX = (v0x * t) / timeDilation; + const translateY = (v0y * t + 0.5 * scaledGravity * t * t) / timeDilation; + + return { + transform: [{ scale }, { translateX }, { translateY }, { rotate }], + }; + }); + + return ( + + + + ); +}; + +const neverRerender = () => true; +export default React.memo(GravityEmoji, neverRerender); diff --git a/src/components/gas/GasSpeedButton.js b/src/components/gas/GasSpeedButton.js index 4bff48b0017..1634929098f 100644 --- a/src/components/gas/GasSpeedButton.js +++ b/src/components/gas/GasSpeedButton.js @@ -1,10 +1,13 @@ +/* eslint-disable no-nested-ternary */ /* eslint-disable no-undef */ import AnimateNumber from '@bankify/react-native-animate-number'; import lang from 'i18n-js'; import { isEmpty, isNaN, isNil, upperFirst } from 'lodash'; import makeColorMoreChill from 'make-color-more-chill'; +import { AnimatePresence, MotiView } from 'moti'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { InteractionManager, Keyboard } from 'react-native'; +import { Easing } from 'react-native-reanimated'; import { darkModeThemeColors } from '../../styles/colors'; import { ButtonPressAnimation } from '../animations'; import { ChainBadge, CoinIcon } from '../coin-icon'; @@ -34,6 +37,8 @@ import styled from '@/styled-thing'; import { fonts, fontWithWidth, margin, padding } from '@/styles'; import { ethereumUtils, gasUtils } from '@/utils'; import { getNetworkObj } from '@/networks'; +import { IS_ANDROID } from '@/env'; +import { ContextMenu } from '../context-menu'; const { GAS_EMOJIS, @@ -92,7 +97,13 @@ const ChainBadgeContainer = styled.View.attrs({ ...margin.object(0), }); -const NativeCoinIconWrapper = styled(Column)(margin.object(1.5, 5, 0, 0)); +const NativeCoinIconWrapper = styled(Column).attrs({})({ + ...margin.object(1, 5, 0, 0), + alignItems: 'center', + height: 18, + justifyContent: 'center', + width: 18, +}); const Container = styled(Column).attrs({ alignItems: 'center', @@ -249,6 +260,7 @@ const GasSpeedButton = ({ shouldOpenCustomGasSheet.focusTo, flashbotTransaction, speeds, + fallbackColor, ]); const openCustomOptions = useCallback( @@ -377,6 +389,25 @@ const GasSpeedButton = ({ [handlePressSpeedOption] ); + const handlePressActionSheet = useCallback( + buttonIndex => { + switch (buttonIndex) { + case 0: + handlePressSpeedOption(NORMAL); + break; + case 1: + handlePressSpeedOption(FAST); + break; + case 2: + handlePressSpeedOption(URGENT); + break; + case 3: + handlePressSpeedOption(CUSTOM); + } + }, + [handlePressSpeedOption] + ); + const nativeFeeCurrency = useMemo(() => { switch (currentNetwork) { case networkTypes.polygon: @@ -399,6 +430,8 @@ const GasSpeedButton = ({ const menuConfig = useMemo(() => { const menuOptions = speedOptions.map(gasOption => { + if (IS_ANDROID) return gasOption; + const totalGwei = add( gasFeeParamsBySpeed[gasOption]?.maxBaseFee?.gwei, gasFeeParamsBySpeed[gasOption]?.maxPriorityFeePerGas?.gwei @@ -441,6 +474,7 @@ const GasSpeedButton = ({ gasFeeParamsBySpeed, selectedGasFeeOption, speedOptions, + isL2, ]); const gasOptionsAvailable = useMemo(() => speedOptions.length > 1, [ @@ -477,6 +511,24 @@ const GasSpeedButton = ({ /> ); if (!gasOptionsAvailable || gasIsNotReady) return pager; + + if (IS_ANDROID) { + return ( + + {pager} + + ); + } + return ( - {currentNetwork === Network.mainnet ? ( - - ) : ( - - )} + + {!!currentNetwork && ( + + {currentNetwork === Network.mainnet ? ( + + ) : ( + + )} + + )} + diff --git a/src/components/icons/Icon.js b/src/components/icons/Icon.js index 6fe64eeb229..c22b506cf9f 100644 --- a/src/components/icons/Icon.js +++ b/src/components/icons/Icon.js @@ -76,6 +76,9 @@ import TabDiscoverInnerFill from './svg/TabDiscoverInnerFill'; import TabHome from './svg/TabHome'; import TabHomeInner from './svg/TabHomeInner'; import TabHomeInnerFill from './svg/TabHomeInnerFill'; +import TabPoints from './svg/TabPoints'; +import TabPointsInner from './svg/TabPointsInner'; +import TabPointsInnerFill from './svg/TabPointsInnerFill'; import TelegramIcon from './svg/TelegramIcon'; import ThreeDotsIcon from './svg/ThreeDotsIcon'; import TouchIdIcon from './svg/TouchIdIcon'; @@ -166,6 +169,9 @@ const IconTypes = { tabHome: TabHome, tabHomeInner: TabHomeInner, tabHomeInnerFill: TabHomeInnerFill, + tabPoints: TabPoints, + tabPointsInner: TabPointsInner, + tabPointsInnerFill: TabPointsInnerFill, telegram: TelegramIcon, threeDots: ThreeDotsIcon, touchid: TouchIdIcon, diff --git a/src/components/icons/TabBarIcon.tsx b/src/components/icons/TabBarIcon.tsx index 0ac52322009..7b80c8ec28d 100644 --- a/src/components/icons/TabBarIcon.tsx +++ b/src/components/icons/TabBarIcon.tsx @@ -16,16 +16,27 @@ type TabBarIconProps = { icon: string; index: number; reanimatedPosition: SharedValue; + hideShadow?: boolean; + size?: number; + tintBackdrop?: string; + tintOpacity?: number; }; export function TabBarIcon({ accentColor, + hideShadow, icon, index, reanimatedPosition, + size, + tintBackdrop, + tintOpacity, }: TabBarIconProps) { const { colors, isDarkMode } = useTheme(); + const hasTransparentInnerFill = + icon === 'tabDiscover' || icon === 'tabPoints'; + const outlineColor = isDarkMode ? globalColors.blueGrey60 : globalColors.blueGrey70; @@ -78,21 +89,24 @@ export function TabBarIcon({ [index - 0.7, index - 0.3, index, index + 0.3, index + 0.7], [ isDarkMode ? outlineColor : '#FEFEFE', - icon === 'tabDiscover' - ? isDarkMode - ? '#171819' - : '#FEFEFE' - : accentColor, - icon === 'tabDiscover' - ? isDarkMode - ? '#171819' - : '#FEFEFE' - : accentColor, - icon === 'tabDiscover' - ? isDarkMode - ? '#171819' - : '#FEFEFE' - : accentColor, + tintBackdrop || + (hasTransparentInnerFill + ? isDarkMode + ? '#171819' + : '#FEFEFE' + : accentColor), + tintBackdrop || + (hasTransparentInnerFill + ? isDarkMode + ? '#171819' + : '#FEFEFE' + : accentColor), + tintBackdrop || + (hasTransparentInnerFill + ? isDarkMode + ? '#171819' + : '#FEFEFE' + : accentColor), isDarkMode ? outlineColor : '#FEFEFE', ] ); @@ -117,7 +131,7 @@ export function TabBarIcon({ const opacity = interpolate( reanimatedPosition.value, [index - 0.7, index - 0.3, index, index + 0.3, index + 0.7], - [0, 0.25, 0.25, 0.25, 0] + [0, tintOpacity ?? 0.25, tintOpacity ?? 0.25, tintOpacity ?? 0.25, 0] ); return { @@ -157,18 +171,20 @@ export function TabBarIcon({ }); return ( - - - + + + - }> + } + > - {icon === 'tabDiscover' && ( + {hasTransparentInnerFill && ( - }> + }> - {icon !== 'tabDiscover' && ( + {!hasTransparentInnerFill && ( - }> + } + > { + return ( + + + + ); +}; + +export default TabPoints; diff --git a/src/components/icons/svg/TabPointsInner.js b/src/components/icons/svg/TabPointsInner.js new file mode 100644 index 00000000000..4a12db3d022 --- /dev/null +++ b/src/components/icons/svg/TabPointsInner.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Path } from 'react-native-svg'; +import Svg from '../Svg'; + +const TabPointsInner = ({ + colors = undefined, + color = colors.white, + size = 28, +}) => { + return ( + + + + ); +}; + +export default TabPointsInner; diff --git a/src/components/icons/svg/TabPointsInnerFill.js b/src/components/icons/svg/TabPointsInnerFill.js new file mode 100644 index 00000000000..6c511baadd4 --- /dev/null +++ b/src/components/icons/svg/TabPointsInnerFill.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { Path } from 'react-native-svg'; +import Svg from '../Svg'; + +const TabPointsInnerFill = ({ + colors = undefined, + color = colors.black, + size = 28, +}) => { + return ( + + + + ); +}; + +export default TabPointsInnerFill; diff --git a/src/components/info-alert/info-alert.tsx b/src/components/info-alert/info-alert.tsx index 4dd3a9ec84e..9b3300341ff 100644 --- a/src/components/info-alert/info-alert.tsx +++ b/src/components/info-alert/info-alert.tsx @@ -17,7 +17,7 @@ export const InfoAlert: React.FC = ({ style={{ gap: 12, borderWidth: 2, - borderColor: useForegroundColor('separatorTertiary'), + borderColor: 'red', }} flexDirection="row" borderRadius={20} @@ -29,8 +29,8 @@ export const InfoAlert: React.FC = ({ {rightIcon} - - + + {title} diff --git a/src/components/list/NoResults.tsx b/src/components/list/NoResults.tsx index ca4b34b454f..77c6131940d 100644 --- a/src/components/list/NoResults.tsx +++ b/src/components/list/NoResults.tsx @@ -3,8 +3,8 @@ import React from 'react'; import { neverRerender } from '@/utils'; import { Inset, Stack, Text } from '@/design-system'; import { useTheme } from '@/theme'; -import { useAssetsInWallet } from '@/hooks'; import { logger } from '@/logger'; +import { useUserAssetCount } from '@/resources/assets/useUserAssetCount'; export enum NoResultsType { Discover = 'discover', @@ -20,7 +20,7 @@ export const NoResults = ({ type: NoResultsType; }) => { const { colors } = useTheme(); - const assets = useAssetsInWallet(); + const { data: assetCount } = useUserAssetCount(); let title; let description; @@ -31,7 +31,7 @@ export const NoResults = ({ break; case NoResultsType.Swap: title = lang.t('exchange.no_results.nothing_found'); - if (assets.length) { + if (assetCount) { description = onL2 ? lang.t('exchange.no_results.description_l2') : lang.t('exchange.no_results.description'); diff --git a/src/components/positions/PositionsCard.tsx b/src/components/positions/PositionsCard.tsx index 5efdd314261..67ef48e329f 100644 --- a/src/components/positions/PositionsCard.tsx +++ b/src/components/positions/PositionsCard.tsx @@ -72,7 +72,7 @@ export const PositionCard = ({ position }: PositionCardProps) => { }, [navigate, position]); const depositTokens: CoinStackToken[] = useMemo(() => { - let tokens: CoinStackToken[] = []; + const tokens: CoinStackToken[] = []; position.deposits.forEach((deposit: RainbowDeposit) => { deposit.underlying.forEach(({ asset }) => { tokens.push({ diff --git a/src/components/profile/ProfileMasthead.js b/src/components/profile/ProfileMasthead.js deleted file mode 100644 index 402ba43b89d..00000000000 --- a/src/components/profile/ProfileMasthead.js +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useCallback } from 'react'; -import Divider from '../Divider'; -import { ButtonPressAnimation } from '../animations'; -import { Icon } from '../icons'; -import { Centered, Column, Row } from '../layout'; -import { TruncatedText } from '../text'; -import AvatarCircle from './AvatarCircle'; -import { useAccountProfile, useDimensions, useOnAvatarPress } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { abbreviations } from '@/utils'; -import { useForegroundColor } from '@/design-system'; - -// NOTE: -// If you’re trying to edit this file for iOS and you’re not seeing any changes, -// that’s because iOS is using the Swift version — TransactionListViewHeader. -// Only Android is using this file at the moment. - -const dropdownArrowWidth = 21; - -const AccountName = styled(TruncatedText).attrs({ - align: 'left', - firstSectionLength: abbreviations.defaultNumCharsPerSection, - letterSpacing: 'roundedMedium', - size: 'big', - truncationLength: 4, - weight: 'bold', -})({ - height: android ? 38 : 33, - marginBottom: android ? 10 : 1, - marginTop: android ? -10 : -1, - maxWidth: ({ deviceWidth }) => deviceWidth - dropdownArrowWidth - 60, - paddingRight: 6, -}); - -const DropdownArrow = styled(Centered)({ - height: 5, - marginTop: 11, - width: dropdownArrowWidth, -}); - -const ProfileMastheadDivider = styled(Divider).attrs( - ({ theme: { colors } }) => ({ - color: colors.rowDividerLight, - }) -)({ - marginTop: 19, - bottom: 0, - position: 'absolute', -}); - -export default function ProfileMasthead({ - recyclerListRef, - showBottomDivider = true, -}) { - const { width: deviceWidth } = useDimensions(); - const { navigate } = useNavigation(); - const { - accountColor, - accountSymbol, - accountName, - accountImage, - } = useAccountProfile(); - - const { - onAvatarPress, - avatarActionSheetOptions, - onSelectionCallback, - } = useOnAvatarPress(); - - const iconColor = useForegroundColor('secondary60 (Deprecated)'); - - const handlePressAvatar = useCallback(() => { - recyclerListRef?.scrollToTop(true); - setTimeout( - onAvatarPress, - recyclerListRef?.getCurrentScrollOffset() > 0 ? 200 : 1 - ); - }, [onAvatarPress, recyclerListRef]); - - const handlePressChangeWallet = useCallback(() => { - navigate(Routes.CHANGE_WALLET_SHEET); - }, [navigate]); - - return ( - - {/* [AvatarCircle -> ImageAvatar -> ImgixImage], so no need to sign accountImage here. */} - - - - - {accountName} - - - - - - - {showBottomDivider && } - - ); -} diff --git a/src/components/profile/index.js b/src/components/profile/index.js index c492a1b68f3..0fea7d824f4 100644 --- a/src/components/profile/index.js +++ b/src/components/profile/index.js @@ -1,3 +1,2 @@ export { default as AvatarCircle } from './AvatarCircle'; export { default as ProfileAction } from './ProfileAction'; -export { default as ProfileMasthead } from './ProfileMasthead'; diff --git a/src/components/remote-promo-sheet/RemotePromoSheet.tsx b/src/components/remote-promo-sheet/RemotePromoSheet.tsx new file mode 100644 index 00000000000..bcb3b71efb7 --- /dev/null +++ b/src/components/remote-promo-sheet/RemotePromoSheet.tsx @@ -0,0 +1,214 @@ +import React, { useCallback, useEffect } from 'react'; +import { useRoute, RouteProp } from '@react-navigation/native'; +import { get } from 'lodash'; + +import { useNavigation } from '@/navigation/Navigation'; +import { PromoSheet } from '@/components/PromoSheet'; +import { useTheme } from '@/theme'; +import { CampaignCheckResult } from './checkForCampaign'; +import { usePromoSheetQuery } from '@/resources/promoSheet/promoSheetQuery'; +import { maybeSignUri } from '@/handlers/imgix'; +import { campaigns } from '@/storage'; +import { delay } from '@/utils/delay'; +import { Linking } from 'react-native'; +import Routes from '@/navigation/routesNames'; +import { Language } from '@/languages'; +import { useAccountSettings } from '@/hooks'; + +const DEFAULT_HEADER_HEIGHT = 285; +const DEFAULT_HEADER_WIDTH = 390; + +type RootStackParamList = { + RemotePromoSheet: CampaignCheckResult; +}; + +type Item = { + title: Record; + description: Record; + icon: string; + gradient?: string; +}; + +const enum ButtonType { + Internal = 'Internal', + External = 'External', +} + +const getKeyForLanguage = ( + key: string, + promoSheet: any, + language: Language +) => { + if (!promoSheet) { + return ''; + } + + const objectOrPrimitive = get(promoSheet, key); + if (typeof objectOrPrimitive === 'undefined') { + return ''; + } + + if (objectOrPrimitive[language]) { + return objectOrPrimitive[language]; + } + + return objectOrPrimitive[Language.EN_US] ?? ''; +}; + +export function RemotePromoSheet() { + const { colors } = useTheme(); + const { goBack, navigate } = useNavigation(); + const { params } = useRoute< + RouteProp + >(); + const { campaignId, campaignKey } = params; + const { language } = useAccountSettings(); + + useEffect(() => { + return () => { + campaigns.set(['isCurrentlyShown'], false); + }; + }, []); + + const { data, error } = usePromoSheetQuery( + { + id: campaignId, + }, + { + enabled: !!campaignId, + } + ); + + const getButtonForType = (type: ButtonType) => { + switch (type) { + default: + case ButtonType.Internal: + return () => internalNavigation(); + case ButtonType.External: + return () => externalNavigation(); + } + }; + + const externalNavigation = useCallback(() => { + Linking.openURL(data?.promoSheet?.primaryButtonProps.props.url); + }, []); + + const internalNavigation = useCallback(() => { + goBack(); + + delay(300).then(() => + navigate( + (Routes as any)[data?.promoSheet?.primaryButtonProps.props.route], + { + ...(data?.promoSheet?.primaryButtonProps.props.options || {}), + } + ) + ); + }, [goBack, navigate, data?.promoSheet]); + + if (!data?.promoSheet || error) { + return null; + } + + const { + accentColor: accentColorString, + backgroundColor: backgroundColorString, + sheetHandleColor: sheetHandleColorString, + backgroundImage, + headerImage, + headerImageAspectRatio, + items, + primaryButtonProps, + secondaryButtonProps, + } = data.promoSheet; + + const accentColor = + (colors as { [key: string]: any })[accentColorString as string] ?? + accentColorString; + + const backgroundColor = + (colors as { [key: string]: any })[backgroundColorString as string] ?? + backgroundColorString; + + const sheetHandleColor = + (colors as { [key: string]: any })[sheetHandleColorString as string] ?? + sheetHandleColorString; + + const backgroundSignedImageUrl = backgroundImage?.url + ? maybeSignUri(backgroundImage.url) + : undefined; + + const headerSignedImageUrl = headerImage?.url + ? maybeSignUri(headerImage.url) + : undefined; + + return ( + { + const title = getKeyForLanguage('title', item, language as Language); + const description = getKeyForLanguage( + 'description', + item, + language as Language + ); + + let gradient = undefined; + if (item.gradient) { + gradient = get(colors.gradients, item.gradient); + } + + return { + ...item, + title, + description, + gradient, + }; + })} + /> + ); +} diff --git a/src/components/remote-promo-sheet/RemotePromoSheetProvider.tsx b/src/components/remote-promo-sheet/RemotePromoSheetProvider.tsx new file mode 100644 index 00000000000..f18975e0e40 --- /dev/null +++ b/src/components/remote-promo-sheet/RemotePromoSheetProvider.tsx @@ -0,0 +1,78 @@ +import React, { + useEffect, + createContext, + PropsWithChildren, + useCallback, + useContext, +} from 'react'; +import { IS_TESTING } from 'react-native-dotenv'; +import { InteractionManager } from 'react-native'; +import { noop } from 'lodash'; + +import { REMOTE_PROMO_SHEETS, useExperimentalFlag } from '@/config'; +import { logger } from '@/logger'; +import { campaigns } from '@/storage'; +import { checkForCampaign } from '@/components/remote-promo-sheet/checkForCampaign'; +import { runFeatureUnlockChecks } from '@/handlers/walletReadyEvents'; +import { runLocalCampaignChecks } from './localCampaignChecks'; + +interface WalletReadyContext { + isWalletReady: boolean; + runChecks: () => void; +} + +export const RemotePromoSheetContext = createContext({ + isWalletReady: false, + runChecks: noop, +}); + +type WalletReadyProvider = PropsWithChildren & WalletReadyContext; + +export const RemotePromoSheetProvider = ({ + isWalletReady = false, + children, +}: WalletReadyProvider) => { + const remotePromoSheets = useExperimentalFlag(REMOTE_PROMO_SHEETS); + + const runChecks = useCallback(async () => { + if (!isWalletReady) return; + + InteractionManager.runAfterInteractions(async () => { + setTimeout(async () => { + if (IS_TESTING === 'true') return; + + // Stop checking for promo sheets if the exp. flag is toggled off + if (!remotePromoSheets) { + logger.info('Campaigns: remote promo sheets is disabled'); + return; + } + + const showedFeatureUnlock = await runFeatureUnlockChecks(); + if (showedFeatureUnlock) return; + + const showedLocalPromo = await runLocalCampaignChecks(); + if (showedLocalPromo) return; + + checkForCampaign(); + }, 2_000); + }); + }, [isWalletReady, remotePromoSheets]); + + useEffect(() => { + runChecks(); + + return () => { + campaigns.remove(['lastShownTimestamp']); + campaigns.set(['isCurrentlyShown'], false); + }; + }, [runChecks]); + + return ( + + {children} + + ); +}; + +export const useRemotePromoSheetContext = () => + useContext(RemotePromoSheetContext); diff --git a/src/components/remote-promo-sheet/check-fns/hasNftOffers.ts b/src/components/remote-promo-sheet/check-fns/hasNftOffers.ts new file mode 100644 index 00000000000..0d977b313ee --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/hasNftOffers.ts @@ -0,0 +1,15 @@ +import store from '@/redux/store'; +import { fetchNftOffers } from '@/resources/reservoir/nftOffersQuery'; + +export async function hasNftOffers(): Promise { + const { accountAddress } = store.getState().settings; + + try { + const data = await fetchNftOffers({ walletAddress: accountAddress }); + if (!data?.nftOffers) return false; + + return data?.nftOffers?.length > 1; + } catch (e) { + return false; + } +} diff --git a/src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts b/src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts new file mode 100644 index 00000000000..8c3d31f1698 --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/hasNonZeroAssetBalance.ts @@ -0,0 +1,38 @@ +import store from '@/redux/store'; +import { EthereumAddress } from '@/entities'; +import { ActionFn } from '../checkForCampaign'; +import { fetchUserAssets } from '@/resources/assets/UserAssetsQuery'; +import { Network } from '@/helpers'; + +type props = { + assetAddress: EthereumAddress; + network?: Network; +}; + +export const hasNonZeroAssetBalance: ActionFn = async ({ + assetAddress, + network, +}) => { + const { accountAddress, nativeCurrency } = store.getState().settings; + + const assets = await fetchUserAssets({ + address: accountAddress, + currency: nativeCurrency, + connectedToHardhat: false, + }); + if (!assets || Object.keys(assets).length === 0) return false; + + const desiredAsset = Object.values(assets).find(asset => { + if (!network) { + return asset.uniqueId.toLowerCase() === assetAddress.toLowerCase(); + } + + return ( + asset.uniqueId.toLowerCase() === assetAddress.toLowerCase() && + asset.network === network + ); + }); + if (!desiredAsset) return false; + + return Number(desiredAsset.balance?.amount) > 0; +}; diff --git a/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts b/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts new file mode 100644 index 00000000000..2d0976b21df --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/hasNonZeroTotalBalance.ts @@ -0,0 +1,16 @@ +import store from '@/redux/store'; +import { fetchUserAssets } from '@/resources/assets/UserAssetsQuery'; + +export const hasNonZeroTotalBalance = async (): Promise => { + const { accountAddress, nativeCurrency } = store.getState().settings; + + const assets = await fetchUserAssets({ + address: accountAddress, + currency: nativeCurrency, + connectedToHardhat: false, + }); + + if (!assets || Object.keys(assets).length === 0) return false; + + return Object.values(assets).some(asset => Number(asset.balance?.amount) > 0); +}; diff --git a/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts new file mode 100644 index 00000000000..be6dcdff593 --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/hasSwapTxn.ts @@ -0,0 +1,17 @@ +import type { EthereumAddress, RainbowTransaction } from '@/entities'; +import store from '@/redux/store'; + +// Rainbow Router +const RAINBOW_ROUTER_ADDRESS: EthereumAddress = + '0x00000000009726632680fb29d3f7a9734e3010e2'; + +const isSwapTx = (tx: RainbowTransaction): boolean => + tx.to?.toLowerCase() === RAINBOW_ROUTER_ADDRESS; + +export const hasSwapTxn = async (): Promise => { + const { transactions } = store.getState().data; + + if (!transactions.length) return false; + + return !!transactions.find(isSwapTx); +}; diff --git a/src/components/remote-promo-sheet/check-fns/index.ts b/src/components/remote-promo-sheet/check-fns/index.ts new file mode 100644 index 00000000000..5e3c68d2474 --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/index.ts @@ -0,0 +1,7 @@ +export * from './hasNftOffers'; +export * from './hasNonZeroAssetBalance'; +export * from './hasNonZeroTotalBalance'; +export * from './hasSwapTxn'; +export * from './isAfterCampaignLaunch'; +export * from './isSelectedWalletReadOnly'; +export * from './isSpecificAddress'; diff --git a/src/components/remote-promo-sheet/check-fns/isAfterCampaignLaunch.ts b/src/components/remote-promo-sheet/check-fns/isAfterCampaignLaunch.ts new file mode 100644 index 00000000000..0f858f7e715 --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/isAfterCampaignLaunch.ts @@ -0,0 +1,5 @@ +import { PromoSheet } from '@/graphql/__generated__/arc'; + +export const isAfterCampaignLaunch = ({ launchDate }: PromoSheet): boolean => { + return new Date() > new Date(launchDate); +}; diff --git a/src/components/remote-promo-sheet/check-fns/isSelectedWalletReadOnly.ts b/src/components/remote-promo-sheet/check-fns/isSelectedWalletReadOnly.ts new file mode 100644 index 00000000000..594399a6f41 --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/isSelectedWalletReadOnly.ts @@ -0,0 +1,13 @@ +import store from '@/redux/store'; +import WalletTypes from '@/helpers/walletTypes'; + +export const isSelectedWalletReadOnly = (): boolean => { + const { selected } = store.getState().wallets; + + // if no selected wallet, we will treat it as a read-only wallet + if (!selected || selected.type === WalletTypes.readOnly) { + return true; + } + + return false; +}; diff --git a/src/components/remote-promo-sheet/check-fns/isSpecificAddress.ts b/src/components/remote-promo-sheet/check-fns/isSpecificAddress.ts new file mode 100644 index 00000000000..6a4111cce2b --- /dev/null +++ b/src/components/remote-promo-sheet/check-fns/isSpecificAddress.ts @@ -0,0 +1,14 @@ +import store from '@/redux/store'; +import { EthereumAddress } from '@/entities'; +import { ActionFn } from '../checkForCampaign'; + +type props = { + addresses: EthereumAddress[]; +}; + +export const isSpecificAddress: ActionFn = async ({ addresses }) => { + const { accountAddress } = store.getState().settings; + return addresses + .map(address => address.toLowerCase()) + .includes(accountAddress.toLowerCase()); +}; diff --git a/src/components/remote-promo-sheet/checkForCampaign.ts b/src/components/remote-promo-sheet/checkForCampaign.ts new file mode 100644 index 00000000000..76eacbc1d20 --- /dev/null +++ b/src/components/remote-promo-sheet/checkForCampaign.ts @@ -0,0 +1,152 @@ +import { InteractionManager } from 'react-native'; +import { Navigation } from '@/navigation'; +import Routes from '@/navigation/routesNames'; +import { fetchPromoSheetCollection } from '@/resources/promoSheet/promoSheetCollectionQuery'; +import { logger } from '@/logger'; +import { PromoSheet, PromoSheetOrder } from '@/graphql/__generated__/arc'; +import { campaigns, device } from '@/storage'; + +import * as fns from './check-fns'; + +type ActionObj = { + fn: string; + outcome: boolean; + props: object; +}; + +export type ActionFn = (props: T) => boolean | Promise; + +export type CampaignCheckResult = { + campaignId: string; + campaignKey: string; +}; + +const TIMEOUT_BETWEEN_PROMOS = 5 * 60 * 1000; // 5 minutes in milliseconds + +const timeBetweenPromoSheets = () => { + const lastShownTimestamp = campaigns.get(['lastShownTimestamp']); + + if (!lastShownTimestamp) return TIMEOUT_BETWEEN_PROMOS; + + return Date.now() - lastShownTimestamp; +}; + +export const checkForCampaign = async () => { + logger.info('Campaigns: Running Checks'); + if (timeBetweenPromoSheets() < TIMEOUT_BETWEEN_PROMOS) { + logger.info('Campaigns: Time between promos has not exceeded timeout'); + return; + } + + let isCurrentlyShown = campaigns.get(['isCurrentlyShown']); + if (isCurrentlyShown) { + logger.info('Campaigns: Promo sheet is already shown'); + return; + } + + const isReturningUser = device.get(['isReturningUser']); + + if (!isReturningUser) { + logger.info('Campaigns: First launch, not showing promo sheet'); + return; + } + + const { promoSheetCollection } = await fetchPromoSheetCollection({ + order: [PromoSheetOrder.PriorityDesc], + }); + + for (const promo of promoSheetCollection?.items || []) { + if (!promo) continue; + logger.info(`Campaigns: Checking ${promo.sys.id}`); + const result = await shouldPromptCampaign(promo as PromoSheet); + + logger.info(`Campaigns: ${promo.sys.id} will show: ${result}`); + if (result) { + isCurrentlyShown = campaigns.get(['isCurrentlyShown']); + if (!isCurrentlyShown) { + return triggerCampaign(promo as PromoSheet); + } + } + } +}; + +export const triggerCampaign = async ({ + campaignKey, + sys: { id: campaignId }, +}: PromoSheet) => { + logger.info(`Campaigns: Showing ${campaignKey} Promo`); + + setTimeout(() => { + campaigns.set([campaignKey as string], true); + campaigns.set(['isCurrentlyShown'], true); + campaigns.set(['lastShownTimestamp'], Date.now()); + InteractionManager.runAfterInteractions(() => { + Navigation.handleAction(Routes.REMOTE_PROMO_SHEET, { + campaignId, + campaignKey, + }); + }); + }, 1000); +}; + +export const shouldPromptCampaign = async ( + campaign: PromoSheet +): Promise => { + const { + campaignKey, + sys: { id }, + actions, + } = campaign; + + // if we aren't given proper campaign data or actions to check against, exit early here + if (!campaignKey || !id) return false; + + // sanity check to prevent showing a campaign twice to a user or potentially showing a campaign to a fresh user + const hasShown = campaigns.get([campaignKey]); + + logger.info( + `Campaigns: Checking if we should prompt campaign ${campaignKey}` + ); + + const isPreviewing = actions.some( + (action: ActionObj) => action.fn === 'isPreviewing' + ); + + // If the campaign has been viewed already or it's the first app launch, exit early + if (hasShown && !isPreviewing) { + logger.info(`Campaigns: User has already been shown ${campaignKey}`); + return false; + } + + const actionsArray = actions || ([] as ActionObj[]); + let shouldPrompt = true; + + for (const actionObj of actionsArray) { + const { fn, outcome, props = {} } = actionObj; + const action = __INTERNAL_ACTION_CHECKS[fn]; + if (typeof action === 'undefined') { + continue; + } + + logger.info(`Campaigns: Checking action ${fn}`); + const result = await action({ ...props, ...campaign }); + logger.info( + `Campaigns: [${fn}] matches desired outcome: => ${result === outcome}` + ); + + if (result !== outcome) { + shouldPrompt = false; + break; + } + } + + // if all action checks pass, we will show the promo to the user + return shouldPrompt; +}; + +export const __INTERNAL_ACTION_CHECKS: { + [key: string]: ActionFn; +} = Object.keys(fns).reduce((acc, fnKey) => { + acc[fnKey] = fns[fnKey as keyof typeof fns]; + return acc; +}, {} as { [key: string]: ActionFn }); diff --git a/src/campaigns/campaignChecks.ts b/src/components/remote-promo-sheet/localCampaignChecks.ts similarity index 78% rename from src/campaigns/campaignChecks.ts rename to src/components/remote-promo-sheet/localCampaignChecks.ts index b062736d66d..e25a1884063 100644 --- a/src/campaigns/campaignChecks.ts +++ b/src/components/remote-promo-sheet/localCampaignChecks.ts @@ -1,13 +1,8 @@ -import { - SwapsPromoCampaign, - SwapsPromoCampaignExclusion, -} from './swapsPromoCampaign'; import { NotificationsPromoCampaign } from './notificationsPromoCampaign'; import { analytics } from '@/analytics'; import { logger } from '@/utils'; export enum CampaignKey { - swapsLaunch = 'swaps_launch', notificationsLaunch = 'notifications_launch', } @@ -22,9 +17,7 @@ export enum GenericCampaignCheckResponse { nonstarter = 'nonstarter', } -export type CampaignCheckResponse = - | GenericCampaignCheckResponse - | SwapsPromoCampaignExclusion; +export type CampaignCheckResponse = GenericCampaignCheckResponse; export interface Campaign { action(): Promise; // Function to call on activating the campaign @@ -34,12 +27,9 @@ export interface Campaign { } // the ordering of this list is IMPORTANT, this is the order that campaigns will be run -export const activeCampaigns: Campaign[] = [ - SwapsPromoCampaign, - NotificationsPromoCampaign, -]; +export const activeCampaigns: Campaign[] = [NotificationsPromoCampaign]; -export const runCampaignChecks = async (): Promise => { +export const runLocalCampaignChecks = async (): Promise => { logger.log('Campaigns: Running Checks'); for (const campaign of activeCampaigns) { const response = await campaign.check(); diff --git a/src/campaigns/notificationsPromoCampaign.ts b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts similarity index 98% rename from src/campaigns/notificationsPromoCampaign.ts rename to src/components/remote-promo-sheet/notificationsPromoCampaign.ts index ad59f1ca8cd..ad46a99c731 100644 --- a/src/campaigns/notificationsPromoCampaign.ts +++ b/src/components/remote-promo-sheet/notificationsPromoCampaign.ts @@ -4,7 +4,7 @@ import { CampaignCheckType, CampaignKey, GenericCampaignCheckResponse, -} from './campaignChecks'; +} from './localCampaignChecks'; import { RainbowWallet } from '@/model/wallet'; import { Navigation } from '@/navigation'; import { logger } from '@/logger'; diff --git a/src/components/send/SendAssetFormCollectible.js b/src/components/send/SendAssetFormCollectible.js index 0ff2af021b4..7c6f84c29a5 100644 --- a/src/components/send/SendAssetFormCollectible.js +++ b/src/components/send/SendAssetFormCollectible.js @@ -6,6 +6,7 @@ import { Column } from '../layout'; import { useDimensions, useImageMetadata } from '@/hooks'; import styled from '@/styled-thing'; import { padding, position } from '@/styles'; +import { IS_ANDROID } from '@/env'; const defaultImageDimensions = { height: 512, width: 512 }; @@ -127,9 +128,11 @@ export default function SendAssetFormCollectible({ {buttonRenderer} {txSpeedRenderer} - - - + {!IS_ANDROID && ( + + + + )} ); diff --git a/src/components/sheet/SlackSheet.js b/src/components/sheet/SlackSheet.js index 07e7795744b..2329026d2b2 100644 --- a/src/components/sheet/SlackSheet.js +++ b/src/components/sheet/SlackSheet.js @@ -68,7 +68,9 @@ const Content = styled.ScrollView.attrs(({ limitScrollViewContent }) => ({ scrollEventThrottle: 16, }))(({ contentHeight, deviceHeight, backgroundColor, removeTopPadding }) => ({ backgroundColor: backgroundColor, - ...(contentHeight ? { height: deviceHeight + contentHeight } : {}), + ...(contentHeight + ? { height: deviceHeight + contentHeight } + : { height: '100%' }), paddingTop: removeTopPadding ? 0 : SheetHandleFixedToTopHeight, width: '100%', })); diff --git a/src/components/sheet/sheet-action-buttons/SheetActionButton.tsx b/src/components/sheet/sheet-action-buttons/SheetActionButton.tsx index ec15dcb37de..c8b8022fbf7 100644 --- a/src/components/sheet/sheet-action-buttons/SheetActionButton.tsx +++ b/src/components/sheet/sheet-action-buttons/SheetActionButton.tsx @@ -24,6 +24,7 @@ type Props = PropsWithChildren<{ label?: string; lightShadows?: boolean; marginBottom?: number; + newShadows?: boolean; nftShadows?: boolean; onPress?: () => void; scaleTo?: number; @@ -45,7 +46,7 @@ const addChartsStyling = (isCharts: boolean) => const Button = styled(Centered)( ({ isCharts, size }: { isCharts?: boolean; size?: string }) => ({ ...addChartsStyling(!!isCharts), - height: size === 'big' ? 56 : 46, + height: size === 'big' ? 52 : 46, }) ); @@ -53,7 +54,7 @@ const Content = styled(RowWithMargins).attrs({ align: 'center', margin: 4, })({ - height: ({ size }: Pick) => (size === 'big' ? 56 : 46), + height: ({ size }: Pick) => (size === 'big' ? 52 : 46), paddingBottom: ({ label }: Pick) => label && containsEmoji(label) ? 2.5 : 1, paddingHorizontal: 19, @@ -76,7 +77,7 @@ const WhiteButtonGradient = React.memo( ); const SheetActionButton: React.FC = ({ - borderRadius = 56, + borderRadius = 52, children, color: givenColor, disabled = false, @@ -89,6 +90,7 @@ const SheetActionButton: React.FC = ({ label = null, lightShadows, onPress, + newShadows, nftShadows, scaleTo = 0.9, size = null, @@ -105,7 +107,12 @@ const SheetActionButton: React.FC = ({ const isWhite = color === colors.white; const textColor = givenTextColor || colors.whiteLabel; const shadowsForButtonColor = useMemo(() => { - if (nftShadows) { + if (newShadows) { + return [ + [0, 2, 6, colors.trueBlack, 0.02], + [0, 10, 30, isDarkMode ? colors.shadow : color, 0.4], + ]; + } else if (nftShadows) { return [[0, 10, 30, colors.alpha(colors.shadowBlack, 0.3)]]; } else if (!forceShadows && (disabled || isTransparent)) { return [[0, 0, 0, colors.transparent, 0]]; @@ -128,6 +135,7 @@ const SheetActionButton: React.FC = ({ isTransparent, isDarkMode, lightShadows, + newShadows, nftShadows, isWhite, ]); @@ -136,7 +144,7 @@ const SheetActionButton: React.FC = ({