1
0
mirror of https://gitcode.com/gh_mirrors/re/react-native-pushy.git synced 2025-09-18 00:56:10 +08:00
Code Issues Packages Projects Releases Wiki Activity GitHub Gitee

Compare commits

..

50 Commits

Author SHA1 Message Date
sunny.luo
73e2f72b0f v10.19.1 2024-12-11 17:23:44 +08:00
CrazyBigTree
e6efa55bd5 fix: check module activity null pointer. (#457)
Co-authored-by: 黄世城 <huangshicheng@xgd.com>
2024-12-10 20:42:34 +08:00
sunny.luo
d6b1205fb9 v10.19.0 2024-12-10 20:05:52 +08:00
sunny.luo
0382cfaec3 check activity 2024-12-10 20:03:13 +08:00
sunnylqm
6ee34ebd24 support 0.76 2024-12-07 16:43:22 +08:00
sunnylqm
09df53a6ab update example to 0.76.3 2024-12-06 23:33:04 +08:00
sunny.luo
be93641392 v10.17.1 2024-12-02 16:20:37 +08:00
sunny.luo
946a5db7e9 catch network error 2024-12-02 16:16:18 +08:00
sunnylqm
d4f21a39f5 v10.17.0 2024-11-26 00:53:11 +08:00
sunnylqm
2192000d53 add return value for downloadupdate 2024-11-23 16:09:43 +08:00
sunnylqm
9a49025884 deps 2024-11-16 07:48:03 +08:00
sunny.luo
f97d731a8e 10.15.3 2024-11-13 00:12:42 +08:00
sunny.luo
d2f23ada25 fix testurl 2024-11-12 23:59:16 +08:00
Sunny Luo
45dd8b2974 Update README.md 2024-11-02 10:45:34 +08:00
Sunny Luo
955834a98f fix throwIfEnabled 2024-10-30 12:08:14 +08:00
sunnylqm
7d0e8398e9 rn754 2024-10-23 22:52:48 +08:00
sunnylqm
86612651d2 setup rnoh 2024-10-19 18:09:31 +08:00
sunnylqm
62860b9e74 use bun 2024-10-19 10:46:26 +08:00
sunnylqm
147d4e6cc1 10.15.1 2024-10-19 09:25:02 +08:00
sunnylqm
a13d6aa21a yarn v4 2024-10-19 09:19:40 +08:00
Sunny Luo
60e446d2b3 ensure logger is ready 2024-10-18 12:36:05 +08:00
Sunny Luo
b85889cf22 feat/rn725 (#451)
* 0.72.5

* fix android build

* fix ios build
2024-10-11 23:47:27 +08:00
sunnylqm
a8705ca0e4 cleanup 2024-10-03 21:58:12 +08:00
sunnylqm
04ac3f3384 lint 2024-10-03 21:45:38 +08:00
sunnylqm
94431ee6f7 improve cleanup and rollout 2024-10-03 21:41:47 +08:00
Sunny Luo
2467b0c119 Update provider.tsx 2024-09-30 21:25:08 +08:00
Sunny Luo
b71626d7d4 Update client.ts 2024-09-30 21:23:30 +08:00
sunnylqm
20d09529d2 v10.14.0 2024-09-22 22:57:02 +08:00
sunnylqm
34bc16ad70 add rollout strategy 2024-09-22 21:59:33 +08:00
sunnylqm
a40d627edf v10.13.2 2024-08-26 18:57:21 +08:00
sunnylqm
462a342172 v10.13.1 2024-08-26 18:54:17 +08:00
sunnylqm
94d2e18900 v10.13.0 2024-08-26 18:52:44 +08:00
sunnylqm
d000c40e0f v10.12.0 2024-08-24 15:28:27 +08:00
sunnylqm
5659c79726 v10.11.8 2024-08-21 20:02:47 +08:00
sunnylqm
b20d987473 fix marksuccess 2024-08-21 19:58:28 +08:00
sunnylqm
14c9c0b1f5 v10.11.7 2024-08-16 11:03:25 +08:00
sunnylqm
10178e1e64 chore: Update switchVersion and switchVersionLater to be async functions 2024-08-16 10:49:35 +08:00
sunnylqm
6d980a4c04 v10.11.6 2024-08-06 13:13:05 +08:00
sunnylqm
084fbf35ee fix typo 2024-08-06 13:12:32 +08:00
sunnylqm
d666e8c0f3 v10.11.5 2024-08-01 11:00:39 +08:00
sunnylqm
5e89fb4a25 v10.11.4 2024-07-31 18:06:05 +08:00
sunnylqm
ab01b6010a update example 2024-07-30 09:42:33 +08:00
sunnylqm
92bc830d98 update example 2024-07-29 23:25:33 +08:00
sunnylqm
7531e8ca3a v10.11.3 2024-07-29 22:40:55 +08:00
sunnylqm
b2305cff3f v10.11.2 2024-07-29 00:57:30 +08:00
sunnylqm
c5cdb6031b v10.11.1 2024-07-29 00:45:30 +08:00
sunnylqm
20ebf8979e fix url params 2024-07-29 00:45:10 +08:00
sunnylqm
3929fc2f8e add crunchPngs false 2024-07-28 22:22:21 +08:00
sunnylqm
31ee269717 fix qrcode 2024-07-28 22:20:33 +08:00
sunnylqm
adcd57b6b5 update example 2024-07-28 21:57:33 +08:00
63 changed files with 11649 additions and 11900 deletions

4
.gitignore vendored
View File

@@ -46,3 +46,7 @@ Example/**/.pushy
Example/testHotUpdate/artifacts
yarn-error.log
Example/testHotUpdate/.yarn
android/bin
Example/testHotUpdate/harmony
Example/testHotUpdate/android/app/.cxx

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -1,6 +0,0 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

View File

@@ -1,2 +0,0 @@
BUNDLE_PATH: "vendor/bundle"
BUNDLE_FORCE_RUBY_PLATFORM: 1

View File

@@ -1,4 +1,4 @@
module.exports = {
root: true,
extends: '@react-native-community',
extends: '@react-native',
};

View File

@@ -1,66 +0,0 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
; Ignore "BUCK" generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore polyfills
node_modules/react-native/Libraries/polyfills/.*
; Flow doesn't support platforms
.*/Libraries/Utilities/LoadingView.js
.*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
[untyped]
.*/node_modules/@react-native-community/cli/.*/.*
[include]
[libs]
node_modules/react-native/interface.js
node_modules/react-native/flow/
[options]
emoji=true
exact_by_default=true
format.bracket_spacing=false
module.file_ext=.js
module.file_ext=.json
module.file_ext=.ios.js
munge_underscores=true
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
[lints]
sketchy-null-number=warn
sketchy-null-mixed=warn
sketchy-number=warn
untyped-type-import=warn
nonstrict-import=warn
deprecated-type=warn
unsafe-getters-setters=warn
unnecessary-invariant=warn
[strict]
deprecated-type
nonstrict-import
sketchy-null
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import
[version]
^0.176.3

View File

@@ -1 +0,0 @@
2.7.5

View File

@@ -1,6 +0,0 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby '3.1.1'
gem 'cocoapods', '~> 1.11', '>= 1.11.2'

View File

@@ -1,98 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
rexml
activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
atomos (0.1.3)
claide (1.1.0)
cocoapods (1.12.0)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.12.0)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.6.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.12.0)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
public_suffix (~> 4.0)
typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.5)
cocoapods-downloader (1.6.3)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.1)
cocoapods-trunk (1.6.0)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.2.2)
escape (0.0.4)
ethon (0.16.0)
ffi (>= 1.15.0)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
httpclient (2.8.3)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
json (2.6.3)
minitest (5.18.0)
molinillo (0.8.0)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
public_suffix (4.0.7)
rexml (3.2.5)
ruby-macho (2.5.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
PLATFORMS
ruby
DEPENDENCIES
cocoapods (~> 1.11, >= 1.11.2)
RUBY VERSION
ruby 3.1.1p18
BUNDLED WITH
2.1.4

View File

@@ -1,226 +1,87 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
apply plugin: "com.android.application"
apply plugin: "org.jetbrains.kotlin.android"
apply plugin: "com.facebook.react"
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
autolinkLibrariesWithApp()
}
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and that value will be read here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
/**
* Architectures to build native code for.
*/
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
namespace "com.awesomeproject"
defaultConfig {
applicationId "com.awesomeproject"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
ndkBuild {
arguments "APP_PLATFORM=android-21",
"APP_STL=c++_shared",
"NDK_TOOLCHAIN_VERSION=clang",
"GENERATED_SRC_DIR=$buildDir/generated/source",
"PROJECT_BUILD_DIR=$buildDir",
"REACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
"REACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
"NODE_MODULES_DIR=$rootDir/../node_modules"
cFlags "-Wall", "-Werror", "-fexceptions", "-frtti", "-DWITH_INSPECTOR=1"
cppFlags "-std=c++17"
// Make sure this target name is the same you specify inside the
// src/main/jni/Android.mk file for the `LOCAL_MODULE` variable.
targets "awesomeproject_appmodules"
}
}
if (!enableSeparateBuildPerCPUArchitecture) {
ndk {
abiFilters (*reactNativeArchitectures())
}
}
}
}
if (isNewArchitectureEnabled()) {
// We configure the NDK build only if you decide to opt-in for the New Architecture.
externalNativeBuild {
ndkBuild {
path "$projectDir/src/main/jni/Android.mk"
}
}
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
into("$buildDir/react-ndk/exported")
}
def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
into("$buildDir/react-ndk/exported")
}
afterEvaluate {
// If you wish to add a custom TurboModule or component locally,
// you should uncomment this line.
// preBuild.dependsOn("generateCodegenArtifactsFromSchema")
preDebugBuild.dependsOn(packageReactNdkDebugLibs)
preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
// Due to a bug inside AGP, we have to explicitly set a dependency
// between configureNdkBuild* tasks and the preBuild tasks.
// This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
configureNdkBuildRelease.dependsOn(preReleaseBuild)
configureNdkBuildDebug.dependsOn(preDebugBuild)
reactNativeArchitectures().each { architecture ->
tasks.findByName("configureNdkBuildDebug[${architecture}]")?.configure {
dependsOn("preDebugBuild")
}
tasks.findByName("configureNdkBuildRelease[${architecture}]")?.configure {
dependsOn("preReleaseBuild")
}
}
}
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include (*reactNativeArchitectures())
}
}
signingConfigs {
debug {
@@ -235,94 +96,24 @@ android {
signingConfig signingConfigs.debug
}
release {
crunchPngs false
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 + versionCodes.get(abi)
}
}
}
}
dependencies {
androidTestImplementation('com.wix:detox:+')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation fileTree(dir: "libs", include: ["*.jar"])
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
//noinspection GradleDynamicVersion
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
exclude group:'com.facebook.fbjni'
}
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
if (isNewArchitectureEnabled()) {
// If new architecture is enabled, we let you build RN from source
// Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
// This will be applied to all the imported transtitive dependency.
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(module("com.facebook.react:react-native"))
.using(project(":ReactAndroid"))
.because("On New Architecture we're building React Native from source")
substitute(module("com.facebook.react:hermes-engine"))
.using(project(":ReactAndroid:hermes-engine"))
.because("On New Architecture we're building Hermes from source")
}
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

View File

@@ -1,73 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.awesomeproject;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}

View File

@@ -21,8 +21,14 @@
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="testhotupdate" />
</intent-filter>
</activity>
</application>

View File

@@ -2,7 +2,8 @@ package com.awesomeproject;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
public class MainActivity extends ReactActivity {
@@ -16,33 +17,16 @@ public class MainActivity extends ReactActivity {
}
/**
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
* you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
* (Paper).
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
* (aka React 18) with two boolean flags.
*/
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new MainActivityDelegate(this, getMainComponentName());
}
public static class MainActivityDelegate extends ReactActivityDelegate {
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
super(activity, mainComponentName);
}
@Override
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
return reactRootView;
}
@Override
protected boolean isConcurrentRootEnabled() {
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
return new DefaultReactActivityDelegate(
this,
getMainComponentName(),
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
DefaultNewArchitectureEntryPoint.getFabricEnabled());
}
}

View File

@@ -1,104 +1,69 @@
package com.awesomeproject;
import android.app.Application;
import android.content.Context;
import androidx.annotation.Nullable;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.awesomeproject.newarchitecture.MainApplicationReactNativeHost;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import cn.reactnative.modules.update.UpdateContext;
import cn.reactnative.modules.update.UpdatePackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Nullable
@Override
protected String getJSBundleFile() {
return UpdateContext.getBundleUrl(MainApplication.this);
}
@Override
protected String getJSBundleFile() {
return UpdateContext.getBundleUrl(MainApplication.this);
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
private final ReactNativeHost mNewArchitectureNativeHost =
new MainApplicationReactNativeHost(this);
@Override
public ReactNativeHost getReactNativeHost() {
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
return mNewArchitectureNativeHost;
} else {
return mReactNativeHost;
}
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// If you opted-in for the New Architecture, we enable the TurboModule system
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.awesomeproject.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
}
}

View File

@@ -1,116 +0,0 @@
package com.awesomeproject.newarchitecture;
import android.app.Application;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleProvider;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JSIModuleType;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.fabric.ComponentFactory;
import com.facebook.react.fabric.CoreComponentsRegistry;
import com.facebook.react.fabric.FabricJSIModuleProvider;
import com.facebook.react.fabric.ReactNativeConfig;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.awesomeproject.BuildConfig;
import com.awesomeproject.newarchitecture.components.MainComponentsRegistry;
import com.awesomeproject.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both
* TurboModule delegates and the Fabric Renderer.
*
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
* `newArchEnabled` property). Is ignored otherwise.
*/
public class MainApplicationReactNativeHost extends ReactNativeHost {
public MainApplicationReactNativeHost(Application application) {
super(application);
}
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
// TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
// packages.add(new TurboReactPackage() { ... });
// If you have custom Fabric Components, their ViewManagers should also be loaded here
// inside a ReactPackage.
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@NonNull
@Override
protected ReactPackageTurboModuleManagerDelegate.Builder
getReactPackageTurboModuleManagerDelegateBuilder() {
// Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary
// for the new architecture and to use TurboModules correctly.
return new MainApplicationTurboModuleManagerDelegate.Builder();
}
@Override
protected JSIModulePackage getJSIModulePackage() {
return new JSIModulePackage() {
@Override
public List<JSIModuleSpec> getJSIModules(
final ReactApplicationContext reactApplicationContext,
final JavaScriptContextHolder jsContext) {
final List<JSIModuleSpec> specs = new ArrayList<>();
// Here we provide a new JSIModuleSpec that will be responsible of providing the
// custom Fabric Components.
specs.add(
new JSIModuleSpec() {
@Override
public JSIModuleType getJSIModuleType() {
return JSIModuleType.UIManager;
}
@Override
public JSIModuleProvider<UIManager> getJSIModuleProvider() {
final ComponentFactory componentFactory = new ComponentFactory();
CoreComponentsRegistry.register(componentFactory);
// Here we register a Components Registry.
// The one that is generated with the template contains no components
// and just provides you the one from React Native core.
MainComponentsRegistry.register(componentFactory);
final ReactInstanceManager reactInstanceManager = getReactInstanceManager();
ViewManagerRegistry viewManagerRegistry =
new ViewManagerRegistry(
reactInstanceManager.getOrCreateViewManagers(reactApplicationContext));
return new FabricJSIModuleProvider(
reactApplicationContext,
componentFactory,
ReactNativeConfig.DEFAULT_CONFIG,
viewManagerRegistry);
}
});
return specs;
}
};
}
}

View File

@@ -1,36 +0,0 @@
package com.awesomeproject.newarchitecture.components;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.fabric.ComponentFactory;
import com.facebook.soloader.SoLoader;
/**
* Class responsible to load the custom Fabric Components. This class has native methods and needs a
* corresponding C++ implementation/header file to work correctly (already placed inside the jni/
* folder for you).
*
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
* `newArchEnabled` property). Is ignored otherwise.
*/
@DoNotStrip
public class MainComponentsRegistry {
static {
SoLoader.loadLibrary("fabricjni");
}
@DoNotStrip private final HybridData mHybridData;
@DoNotStrip
private native HybridData initHybrid(ComponentFactory componentFactory);
@DoNotStrip
private MainComponentsRegistry(ComponentFactory componentFactory) {
mHybridData = initHybrid(componentFactory);
}
@DoNotStrip
public static MainComponentsRegistry register(ComponentFactory componentFactory) {
return new MainComponentsRegistry(componentFactory);
}
}

View File

@@ -1,48 +0,0 @@
package com.awesomeproject.newarchitecture.modules;
import com.facebook.jni.HybridData;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.soloader.SoLoader;
import java.util.List;
/**
* Class responsible to load the TurboModules. This class has native methods and needs a
* corresponding C++ implementation/header file to work correctly (already placed inside the jni/
* folder for you).
*
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
* `newArchEnabled` property). Is ignored otherwise.
*/
public class MainApplicationTurboModuleManagerDelegate
extends ReactPackageTurboModuleManagerDelegate {
private static volatile boolean sIsSoLibraryLoaded;
protected MainApplicationTurboModuleManagerDelegate(
ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
super(reactApplicationContext, packages);
}
protected native HybridData initHybrid();
native boolean canCreateTurboModule(String moduleName);
public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
protected MainApplicationTurboModuleManagerDelegate build(
ReactApplicationContext context, List<ReactPackage> packages) {
return new MainApplicationTurboModuleManagerDelegate(context, packages);
}
}
@Override
protected synchronized void maybeLoadOtherSoLibraries() {
if (!sIsSoLibraryLoaded) {
// If you change the name of your application .so file in the Android.mk file,
// make sure you update the name here as well.
SoLoader.loadLibrary("awesomeproject_appmodules");
sIsSoLibraryLoaded = true;
}
}
}

View File

@@ -1,48 +0,0 @@
THIS_DIR := $(call my-dir)
include $(REACT_ANDROID_DIR)/Android-prebuilt.mk
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to include the following autogenerated makefile.
# include $(GENERATED_SRC_DIR)/codegen/jni/Android.mk
include $(CLEAR_VARS)
LOCAL_PATH := $(THIS_DIR)
# You can customize the name of your application .so file here.
LOCAL_MODULE := awesomeproject_appmodules
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)
# If you wish to add a custom TurboModule or Fabric component in your app you
# will have to uncomment those lines to include the generated source
# files from the codegen (placed in $(GENERATED_SRC_DIR)/codegen/jni)
#
# LOCAL_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# LOCAL_SRC_FILES += $(wildcard $(GENERATED_SRC_DIR)/codegen/jni/*.cpp)
# LOCAL_EXPORT_C_INCLUDES += $(GENERATED_SRC_DIR)/codegen/jni
# Here you should add any native library you wish to depend on.
LOCAL_SHARED_LIBRARIES := \
libfabricjni \
libfbjni \
libfolly_runtime \
libglog \
libjsi \
libreact_codegen_rncore \
libreact_debug \
libreact_nativemodule_core \
libreact_render_componentregistry \
libreact_render_core \
libreact_render_debug \
libreact_render_graphics \
librrc_view \
libruntimeexecutor \
libturbomodulejsijni \
libyoga
LOCAL_CFLAGS := -DLOG_TAG=\"ReactNative\" -fexceptions -frtti -std=c++17 -Wall
include $(BUILD_SHARED_LIBRARY)

View File

@@ -1,24 +0,0 @@
#include "MainApplicationModuleProvider.h"
#include <rncore.h>
namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
const std::string moduleName,
const JavaTurboModule::InitParams &params) {
// Here you can provide your own module provider for TurboModules coming from
// either your application or from external libraries. The approach to follow
// is similar to the following (for a library called `samplelibrary`:
//
// auto module = samplelibrary_ModuleProvider(moduleName, params);
// if (module != nullptr) {
// return module;
// }
// return rncore_ModuleProvider(moduleName, params);
return rncore_ModuleProvider(moduleName, params);
}
} // namespace react
} // namespace facebook

View File

@@ -1,16 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <ReactCommon/JavaTurboModule.h>
namespace facebook {
namespace react {
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
const std::string moduleName,
const JavaTurboModule::InitParams &params);
} // namespace react
} // namespace facebook

View File

@@ -1,45 +0,0 @@
#include "MainApplicationTurboModuleManagerDelegate.h"
#include "MainApplicationModuleProvider.h"
namespace facebook {
namespace react {
jni::local_ref<MainApplicationTurboModuleManagerDelegate::jhybriddata>
MainApplicationTurboModuleManagerDelegate::initHybrid(
jni::alias_ref<jhybridobject>) {
return makeCxxInstance();
}
void MainApplicationTurboModuleManagerDelegate::registerNatives() {
registerHybrid({
makeNativeMethod(
"initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid),
makeNativeMethod(
"canCreateTurboModule",
MainApplicationTurboModuleManagerDelegate::canCreateTurboModule),
});
}
std::shared_ptr<TurboModule>
MainApplicationTurboModuleManagerDelegate::getTurboModule(
const std::string name,
const std::shared_ptr<CallInvoker> jsInvoker) {
// Not implemented yet: provide pure-C++ NativeModules here.
return nullptr;
}
std::shared_ptr<TurboModule>
MainApplicationTurboModuleManagerDelegate::getTurboModule(
const std::string name,
const JavaTurboModule::InitParams &params) {
return MainApplicationModuleProvider(name, params);
}
bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
std::string name) {
return getTurboModule(name, nullptr) != nullptr ||
getTurboModule(name, {.moduleName = name}) != nullptr;
}
} // namespace react
} // namespace facebook

View File

@@ -1,38 +0,0 @@
#include <memory>
#include <string>
#include <ReactCommon/TurboModuleManagerDelegate.h>
#include <fbjni/fbjni.h>
namespace facebook {
namespace react {
class MainApplicationTurboModuleManagerDelegate
: public jni::HybridClass<
MainApplicationTurboModuleManagerDelegate,
TurboModuleManagerDelegate> {
public:
// Adapt it to the package you used for your Java class.
static constexpr auto kJavaDescriptor =
"Lcom/awesomeproject/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;";
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
static void registerNatives();
std::shared_ptr<TurboModule> getTurboModule(
const std::string name,
const std::shared_ptr<CallInvoker> jsInvoker) override;
std::shared_ptr<TurboModule> getTurboModule(
const std::string name,
const JavaTurboModule::InitParams &params) override;
/**
* Test-only method. Allows user to verify whether a TurboModule can be
* created by instances of this class.
*/
bool canCreateTurboModule(std::string name);
};
} // namespace react
} // namespace facebook

View File

@@ -1,61 +0,0 @@
#include "MainComponentsRegistry.h"
#include <CoreComponentsRegistry.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/components/rncore/ComponentDescriptors.h>
namespace facebook {
namespace react {
MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {}
std::shared_ptr<ComponentDescriptorProviderRegistry const>
MainComponentsRegistry::sharedProviderRegistry() {
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
// Custom Fabric Components go here. You can register custom
// components coming from your App or from 3rd party libraries here.
//
// providerRegistry->add(concreteComponentDescriptorProvider<
// AocViewerComponentDescriptor>());
return providerRegistry;
}
jni::local_ref<MainComponentsRegistry::jhybriddata>
MainComponentsRegistry::initHybrid(
jni::alias_ref<jclass>,
ComponentFactory *delegate) {
auto instance = makeCxxInstance(delegate);
auto buildRegistryFunction =
[](EventDispatcher::Weak const &eventDispatcher,
ContextContainer::Shared const &contextContainer)
-> ComponentDescriptorRegistry::Shared {
auto registry = MainComponentsRegistry::sharedProviderRegistry()
->createComponentDescriptorRegistry(
{eventDispatcher, contextContainer});
auto mutableRegistry =
std::const_pointer_cast<ComponentDescriptorRegistry>(registry);
mutableRegistry->setFallbackComponentDescriptor(
std::make_shared<UnimplementedNativeViewComponentDescriptor>(
ComponentDescriptorParameters{
eventDispatcher, contextContainer, nullptr}));
return registry;
};
delegate->buildRegistryFunction = buildRegistryFunction;
return instance;
}
void MainComponentsRegistry::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid),
});
}
} // namespace react
} // namespace facebook

View File

@@ -1,32 +0,0 @@
#pragma once
#include <ComponentFactory.h>
#include <fbjni/fbjni.h>
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
namespace facebook {
namespace react {
class MainComponentsRegistry
: public facebook::jni::HybridClass<MainComponentsRegistry> {
public:
// Adapt it to the package you used for your Java class.
constexpr static auto kJavaDescriptor =
"Lcom/awesomeproject/newarchitecture/components/MainComponentsRegistry;";
static void registerNatives();
MainComponentsRegistry(ComponentFactory *delegate);
private:
static std::shared_ptr<ComponentDescriptorProviderRegistry const>
sharedProviderRegistry();
static jni::local_ref<jhybriddata> initHybrid(
jni::alias_ref<jclass>,
ComponentFactory *delegate);
};
} // namespace react
} // namespace facebook

View File

@@ -1,11 +0,0 @@
#include <fbjni/fbjni.h>
#include "MainApplicationTurboModuleManagerDelegate.h"
#include "MainComponentsRegistry.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
return facebook::jni::initialize(vm, [] {
facebook::react::MainApplicationTurboModuleManagerDelegate::
registerNatives();
facebook::react::MainComponentsRegistry::registerNatives();
});
}

View File

@@ -1,59 +1,21 @@
import org.apache.tools.ant.taskdefs.condition.Os
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "31.0.0"
minSdkVersion = 23
compileSdkVersion = 31
targetSdkVersion = 31
kotlinVersion = '1.7.20'
kotlin_version = '1.7.20'
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
}
buildToolsVersion = "35.0.0"
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 34
ndkVersion = "26.1.10909125"
kotlinVersion = "1.9.24"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.1")
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("de.undercouch:gradle-download-task:5.0.1")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
}
}
allprojects {
repositories {
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")
}
mavenCentral {
// We don't want to fetch react-native from Maven Central as there are
// older versions over there.
content {
excludeGroup "com.facebook.react"
}
}
google()
maven {
url("$rootDir/../node_modules/detox/Detox-android")
}
maven { url 'https://www.jitpack.io' }
}
}
apply plugin: "com.facebook.react.rootproject"

View File

@@ -21,11 +21,6 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
@@ -37,4 +32,8 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
newArchEnabled=true
# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,12 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +134,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -193,11 +201,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@@ -205,6 +217,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

View File

@@ -13,8 +13,10 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +27,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -56,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -1,11 +1,7 @@
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
plugins { id("com.facebook.react.settings") }
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
rootProject.name = 'AwesomeProject'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
include(":ReactAndroid")
project(":ReactAndroid").projectDir = file('../node_modules/react-native/ReactAndroid')
include(":ReactAndroid:hermes-engine")
project(":ReactAndroid:hermes-engine").projectDir = file('../node_modules/react-native/ReactAndroid/hermes-engine')
}
includeBuild('../node_modules/@react-native/gradle-plugin')

View File

@@ -1,5 +1,5 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
presets: ['module:@react-native/babel-preset'],
env: {
production: {
plugins: ['react-native-paper/babel'],

BIN
Example/testHotUpdate/bun.lockb Executable file

Binary file not shown.

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
0C06D3B1D2C63EC04B024612 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 77A7A2D881E69AE3DCCE6BFE /* PrivacyInfo.xcprivacy */; };
0C80B921A6F3F58F76C31292 /* libPods-AwesomeProject.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-AwesomeProject.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
@@ -26,6 +27,7 @@
5709B34CF0A7D63546082F79 /* Pods-AwesomeProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject.release.xcconfig"; path = "Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject.release.xcconfig"; sourceTree = "<group>"; };
5B7EB9410499542E8C5724F5 /* Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig"; path = "Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests.debug.xcconfig"; sourceTree = "<group>"; };
5DCACB8F33CDC322A6C60F78 /* libPods-AwesomeProject.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AwesomeProject.a"; sourceTree = BUILT_PRODUCTS_DIR; };
77A7A2D881E69AE3DCCE6BFE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = AwesomeProject/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = AwesomeProject/LaunchScreen.storyboard; sourceTree = "<group>"; };
89C6BE57DB24E9ADA2F236DE /* Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig"; path = "Target Support Files/Pods-AwesomeProject-AwesomeProjectTests/Pods-AwesomeProject-AwesomeProjectTests.release.xcconfig"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -52,6 +54,7 @@
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
77A7A2D881E69AE3DCCE6BFE /* PrivacyInfo.xcprivacy */,
);
name = AwesomeProject;
sourceTree = "<group>";
@@ -120,6 +123,7 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
2177C9C260D54703D642190E /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -168,6 +172,7 @@
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
0C06D3B1D2C63EC04B024612 /* PrivacyInfo.xcprivacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -190,6 +195,23 @@
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
};
2177C9C260D54703D642190E /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AwesomeProject/Pods-AwesomeProject-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -318,7 +340,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -346,7 +368,7 @@
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -354,6 +376,7 @@
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION,
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -374,14 +397,18 @@
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
USE_HERMES = true;
};
name = Debug;
};
@@ -390,7 +417,7 @@
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
@@ -418,9 +445,13 @@
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION,
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -438,14 +469,17 @@
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_CFLAGS = "$(inherited)";
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DFOLLY_NO_CONFIG",
"-DFOLLY_MOBILE=1",
"-DFOLLY_USE_LIBCPP=1",
);
OTHER_LDFLAGS = "$(inherited)";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;
VALIDATE_PRODUCT = YES;
};
name = Release;

View File

@@ -1,8 +1,6 @@
#import <React/RCTBridgeDelegate.h>
#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@property (nonatomic, strong) UIWindow *window;
@interface AppDelegate : RCTAppDelegate
@end

View File

@@ -1,133 +1,28 @@
#import "AppDelegate.h"
#import "RCTPushy.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTAppSetupUtils.h>
#if RCT_NEW_ARCH_ENABLED
#import <React/CoreModulesPlugins.h>
#import <React/RCTCxxBridgeDelegate.h>
#import <React/RCTFabricSurfaceHostingProxyRootView.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <ReactCommon/RCTTurboModuleManager.h>
#import <react/config/ReactNativeConfig.h>
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
RCTTurboModuleManager *_turboModuleManager;
RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTAppSetupPrepareApp(application);
self.moduleName = @"AwesomeProject";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
#if RCT_NEW_ARCH_ENABLED
_contextContainer = std::make_shared<facebook::react::ContextContainer const>();
_reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
_contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
_bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
#endif
NSDictionary *initProps = [self prepareInitialProps];
UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"AwesomeProject", initProps);
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
- (NSURL *)bundleURL
{
// Switch this bool to turn on and off the concurrent root
return true;
#if DEBUG
// 原先DEBUG这里的写法不作修改(所以DEBUG模式下不可热更新)
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [RCTPushy bundleURL]; // <-- 把这里非DEBUG的情况替换为热更新bundle
#endif
}
- (NSDictionary *)prepareInitialProps
{
NSMutableDictionary *initProps = [NSMutableDictionary new];
#ifdef RCT_NEW_ARCH_ENABLED
initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
#endif
return initProps;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [RCTPushy bundleURL];
#endif
}
#if RCT_NEW_ARCH_ENABLED
#pragma mark - RCTCxxBridgeDelegate
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
_turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
delegate:self
jsInvoker:bridge.jsCallInvoker];
return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
}
#pragma mark RCTTurboModuleManagerDelegate
- (Class)getModuleClassFromName:(const char *)name
{
return RCTCoreModulesClassProvider(name);
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
return nullptr;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
initParams:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return nullptr;
}
- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
return RCTAppSetupDefaultModuleFromClass(moduleClass);
}
#endif
@end

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@@ -1,30 +1,36 @@
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
"react-native/scripts/react_native_pods.rb",
{paths: [process.argv[1]]},
)', __dir__]).strip
platform :ios, '12.4'
install! 'cocoapods', :deterministic_uuids => false
platform :ios, min_ios_version_supported
prepare_react_native_project!
production = ENV["PRODUCTION"] == "1"
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'AwesomeProject' do
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# to enable hermes on iOS, change `false` to `true` and then install pods
:production => production,
:hermes_enabled => flags[:hermes_enabled],
:fabric_enabled => flags[:fabric_enabled],
:flipper_configuration => false,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
post_install do |installer|
react_native_post_install(installer)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,11 @@
// const path = require('path');
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
// const extraNodeModules = {
// react: path.resolve(__dirname, 'node_modules/react'),
// 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
// 'react-native-update': path.resolve(__dirname, '../..'),
// '@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'),
// };
// const watchFolders = [path.resolve(__dirname, '../..')];
/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('metro-config').MetroConfig}
*/
const config = {};
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
// resolver: {
// extraNodeModules,
// },
// watchFolders,
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);

View File

@@ -10,34 +10,45 @@
"test:e2e": "detox test --configuration android.emu.debug",
"lint": "eslint .",
"postinstall": "patch-package",
"apk": "cd android && ./gradlew assembleRelease"
"apk": "cd android && ./gradlew assembleRelease",
"dev:harmony": "react-native bundle-harmony --dev"
},
"dependencies": {
"patch-package": "^6.5.1",
"form-data": "^4.0.1",
"patch-package": "^8.0.0",
"postinstall-postinstall": "^2.1.0",
"react": "18.0.0",
"react-native": "0.69.8",
"react-native-camera-kit": "^14.0.0-beta15",
"react-native-paper": "^5.12.1",
"react-native-safe-area-context": "^4.8.2",
"react-native-update": "^10.10.0",
"react-native-vector-icons": "^10.0.3"
"react": "18.3.1",
"react-native": "0.76.3",
"react-native-camera-kit": "^14.1.0",
"react-native-paper": "^5.12.5",
"react-native-safe-area-context": "^4.14.0",
"react-native-update": "^10.17.1",
"react-native-vector-icons": "^10.2.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@babel/runtime": "^7.21.0",
"@react-native-community/eslint-config": "^3.2.0",
"babel-jest": "^29.5.0",
"detox": "^20.5.0",
"eslint": "^8.36.0",
"jest": "^29.5.0",
"metro-react-native-babel-preset": "^0.76.0",
"pod-install": "^0.1.37",
"prettier": "^2.8.4",
"react-test-renderer": "18.2.0",
"ts-jest": "^29.0.5"
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@react-native-community/cli": "15.0.0-alpha.2",
"@react-native-community/cli-platform-android": "15.0.0-alpha.2",
"@react-native-community/cli-platform-ios": "15.0.0-alpha.2",
"@react-native/babel-preset": "0.76.3",
"@react-native/eslint-config": "0.76.3",
"@react-native/metro-config": "0.76.3",
"@react-native/typescript-config": "0.76.3",
"@types/react": "^18.2.6",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.6.3",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"prettier": "2.8.8",
"react-test-renderer": "18.3.1",
"typescript": "5.7.2"
},
"jest": {
"preset": "react-native"
}
"engines": {
"node": ">=16"
},
"trustedDependencies": [
"postinstall-postinstall"
]
}

View File

@@ -1,13 +0,0 @@
diff --git a/node_modules/react-native/scripts/react_native_pods.rb b/node_modules/react-native/scripts/react_native_pods.rb
index e7c59ad..1461d45 100644
--- a/node_modules/react-native/scripts/react_native_pods.rb
+++ b/node_modules/react-native/scripts/react_native_pods.rb
@@ -420,7 +420,7 @@ def get_react_codegen_spec(options={})
'source' => { :git => '' },
'header_mappings_dir' => './',
'platforms' => {
- 'ios' => '11.0',
+ 'ios' => '12.0',
},
'source_files' => "**/*.{h,mm,cpp}",
'pod_target_xcconfig' => { "HEADER_SEARCH_PATHS" =>

View File

@@ -1,7 +1,8 @@
/* eslint-disable react-native/no-inline-styles */
/* eslint-disable react/react-in-jsx-scope */
import {useCallback, useMemo, useState} from 'react';
import {useState} from 'react';
import {
Alert,
ActivityIndicator,
Modal,
TextInput,
@@ -13,132 +14,127 @@ import {
TouchableOpacity,
} from 'react-native';
import {PushyModule} from 'react-native-update';
import {PushyModule} from 'react-native-update/src/core';
const Hash = '9D5CE6EBA420717BE7E7D308B11F8207681B066C951D68F3994D19828F342474';
const UUID = '00000000-0000-0000-0000-000000000000';
const DownloadUrl =
'http://cos.pgyer.com/697913e94d7441f20c686e2b0996a1aa.apk?sign=7a8f11b1df82cba45c8ac30b1acec88c&t=1680404102&response-content-disposition=attachment%3Bfilename%3DtestHotupdate_1.0.apk';
const CustomDialog = ({title, visible, onConfirm}) => {
if (!visible) {
return null;
}
return (
<View style={styles.overlay}>
<View style={styles.dialog}>
<Text style={styles.title}>{title}</Text>
<TouchableOpacity
testID="done"
style={styles.button}
onLongPress={onConfirm}>
<Text style={styles.buttonText}>确认</Text>
</TouchableOpacity>
</View>
</View>
);
};
export default function TestConsole({visible}) {
export default function TestConsole({visible, onClose}) {
const [text, setText] = useState('');
const [running, setRunning] = useState(false);
const [options, setOptions] = useState();
const [alertVisible, setAlertVisible] = useState(false);
const [alertMsg, setAlertMsg] = useState('');
const NativeTestMethod = useMemo(() => {
return [
const convertCommands = (cmd, params) => {
if (typeof params === 'string') {
return `${cmd}\n${params}`;
}
let paramText = '';
for (const [k, v] of Object.entries(params)) {
paramText += `\n${k}\n${v}`;
}
return `${cmd}${paramText}`;
};
const shortCuts = [
{
name: 'setLocalHashInfo',
invoke: () => {
setText(
`setLocalHashInfo\n${Hash}\n{\"version\":\"1.0.0\",\"size\":\"19M\"}`,
convertCommands('setLocalHashInfo', {
version: '1.0.0',
size: '19M',
}),
);
},
},
{
name: 'getLocalHashInfo',
invoke: () => {
setText(`getLocalHashInfo\n${Hash}`);
setText(convertCommands('getLocalHashInfo', Hash));
},
},
{
name: 'setUuid',
invoke: () => {
setText(`setUuid\n${UUID}`);
setText(convertCommands('setUuid', UUID));
},
},
{
name: 'reloadUpdate',
invoke: () => {
setText('reloadUpdate');
setOptions({hash: Hash});
setText(convertCommands('reloadUpdate', {hash: Hash}));
},
},
{
name: 'setNeedUpdate',
invoke: () => {
setText('setNeedUpdate');
setOptions({hash: Hash});
setText(convertCommands('setNeedUpdate', {hash: Hash}));
},
},
{
name: 'markSuccess',
invoke: () => {
setText('markSuccess');
setOptions(undefined);
setText(convertCommands('markSuccess'));
},
},
{
name: 'downloadPatchFromPpk',
invoke: () => {
setText('downloadPatchFromPpk');
setOptions({updateUrl: DownloadUrl, hash: Hash, originHash: Hash});
setText(
convertCommands('downloadPatchFromPpk', {
updateUrl: DownloadUrl,
hash: Hash,
originHash: Hash,
}),
);
},
},
{
name: 'downloadPatchFromPackage',
invoke: () => {
setText('downloadPatchFromPackage');
setOptions({updateUrl: DownloadUrl, hash: Hash});
setText(
convertCommands('downloadPatchFromPackage', {
updateUrl: DownloadUrl,
hash: Hash,
}),
);
},
},
{
name: 'downloadFullUpdate',
invoke: () => {
setText('downloadFullUpdate');
setOptions({updateUrl: DownloadUrl, hash: Hash});
setText(
convertCommands('downloadFullUpdate', {
updateUrl: DownloadUrl,
hash: Hash,
}),
);
},
},
{
name: 'downloadAndInstallApk',
invoke: () => {
setText('downloadAndInstallApk');
setOptions({url: DownloadUrl, target: Hash, hash: Hash});
setText(
convertCommands('downloadAndInstallApk', {
url: DownloadUrl,
target: Hash,
hash: Hash,
}),
);
},
},
];
}, []);
const renderTestView = useCallback(() => {
const views = [];
for (let i = 0; i < NativeTestMethod.length; i++) {
views.push(
<TouchableOpacity
key={i}
testID={NativeTestMethod[i].name}
onLongPress={() => {
NativeTestMethod[i].invoke();
}}>
<Text>{NativeTestMethod[i].name}</Text>
</TouchableOpacity>,
);
}
return <View>{views}</View>;
}, [NativeTestMethod]);
return (
<Modal visible={visible}>
<SafeAreaView style={{flex: 1, padding: 10}}>
<Text>调试Pushy方法方法名参数值换行</Text>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<Text>调试Pushy方法方法名参数值换行</Text>
<Button title="Close" onPress={onClose} />
</View>
<TextInput
autoCorrect={false}
autoCapitalize="none"
@@ -167,46 +163,49 @@ export default function TestConsole({visible}) {
marginBottom: 5,
}}
testID="submit"
onLongPress={async () => {
onPress={async () => {
setRunning(true);
try {
const inputs = text.split('\n');
const methodName = inputs[0];
let params = [];
let params;
if (inputs.length === 1) {
if (options) {
await PushyModule[methodName](options);
} else {
await PushyModule[methodName]();
}
await PushyModule[methodName]();
} else {
if (inputs.length === 2) {
params = [inputs[1]];
params = inputs[1];
} else {
params = [inputs[1], inputs[2]];
params = {};
for (let i = 1; i < inputs.length; i += 2) {
params[inputs[i]] = inputs[i + 1];
}
console.log({inputs, params});
}
await PushyModule[methodName](...params);
await PushyModule[methodName](params);
}
setAlertVisible(true);
setAlertMsg('done');
Alert.alert('done');
} catch (e) {
setAlertVisible(true);
setAlertMsg(e.message);
Alert.alert(e.message);
}
setRunning(false);
}}>
<Text style={{color: 'white'}}>执行</Text>
</TouchableOpacity>
<Button title="重置" onPress={() => setText('')} />
{renderTestView()}
<CustomDialog
title={alertMsg}
visible={alertVisible}
onConfirm={() => {
setAlertVisible(false);
}}
/>
{
<View>
{shortCuts.map(({name, invoke}, i) => (
<TouchableOpacity
key={i}
testID={name}
onPress={() => {
invoke();
}}>
<Text>{name}</Text>
</TouchableOpacity>
))}
</View>
}
</SafeAreaView>
</Modal>
);

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/no-unstable-nested-components */
/* eslint-disable react-native/no-inline-styles */
import React, {useState} from 'react';
import React, {useRef, useState} from 'react';
import {
StyleSheet,
Platform,
@@ -37,7 +37,7 @@ function App() {
updateInfo,
packageVersion,
currentHash,
parseTestPayload,
parseTestQrCode,
progress: {received, total} = {},
} = usePushy();
const [useDefaultAlert, setUseDefaultAlert] = useState(true);
@@ -47,6 +47,7 @@ function App() {
const snackbarVisible =
!useDefaultAlert && showUpdateSnackbar && updateInfo?.update;
const [showCamera, setShowCamera] = useState(false);
const lastParsedCode = useRef('');
return (
<View style={styles.container}>
@@ -73,9 +74,16 @@ function App() {
style={{minHeight: 320}}
scanBarcode={true}
onReadCode={({nativeEvent: {codeStringValue}}) => {
console.log(codeStringValue);
parseTestPayload(codeStringValue);
// 防止重复扫码
if (lastParsedCode.current === codeStringValue) {
return;
}
lastParsedCode.current = codeStringValue;
setTimeout(() => {
lastParsedCode.current = '';
}, 1000);
setShowCamera(false);
parseTestQrCode(codeStringValue);
}} // optional
showFrame={true} // (default false) optional, show frame with transparent layer (qr code or barcode will be read on this area ONLY), start animation for scanner, that stops when a code has been found. Frame always at center of the screen
laserColor="red" // (default red) optional, color of laser in scanner frame
@@ -116,7 +124,10 @@ function App() {
react-native-update版本{client?.version}
</Text>
</TouchableOpacity>
<TestConsole visible={showTestConsole} />
<TestConsole
visible={showTestConsole}
onClose={() => setShowTestConsole(false)}
/>
{snackbarVisible && (
<Snackbar
visible={snackbarVisible}
@@ -183,6 +194,7 @@ const styles = StyleSheet.create({
const pushyClient = new Pushy({
appKey,
debug: true,
});
export default function Root() {

File diff suppressed because it is too large Load Diff

View File

@@ -8,9 +8,9 @@
### 优势
1. 基于阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。
1. 对中国用户使用阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。对国外用户则智能分流至 cloudflare同样享受高速更新服务。
2. 基于 bsdiff/hdiff 算法创建的**超小更新包**,通常版本迭代后在几十 KB 级别(其他全量热更新服务所需流量通常在几十 MB 级别)。
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。支持新架构。
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。支持新架构(注:安卓 0.73.0 ~ 0.76.0 的新架构因官方 bug 不支持0.73 以下或 0.76.1 以上的新架构可用)
4. 跨越多个版本进行更新时,只需要下载**一个更新包**,不需要逐版本依次更新。
5. 命令行工具 & 网页双端管理,版本发布过程简单便捷,完全可以集成 CI。
6. 支持崩溃回滚,安全可靠。
@@ -33,4 +33,3 @@ $ yarn start
本组件由[React Native 中文网](https://reactnative.cn/)独家发布,如有定制需求可以[联系我们](https://reactnative.cn/about.html#content)。
关于此插件发现任何问题,可以前往[Issues](https://github.com/reactnativecn/react-native-pushy/issues)发帖提问。

View File

@@ -430,6 +430,9 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
if (sub.getName().charAt(0) == '.') {
continue;
}
if (isFileUpdatedWithinDays(sub, 7)) {
continue;
}
if (sub.isFile()) {
sub.delete();
} else {
@@ -441,6 +444,13 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, long[], Void> {
}
}
private boolean isFileUpdatedWithinDays(File file, int days) {
long currentTime = System.currentTimeMillis();
long lastModified = file.lastModified();
long daysInMillis = days * 24 * 60 * 60 * 1000L;
return (currentTime - lastModified) < daysInMillis;
}
@Override
protected Void doInBackground(DownloadTaskParams... params) {
int taskType = params[0].type;

View File

@@ -1,7 +1,7 @@
package cn.reactnative.modules.update;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
@@ -117,8 +117,7 @@ public class UpdateModuleImpl {
public void run() {
try {
updateContext.switchVersion(hash);
Activity activity = mContext.getCurrentActivity();
Application application = activity.getApplication();
final Context application = mContext.getApplicationContext();
ReactInstanceManager instanceManager = updateContext.getCustomReactInstanceManager();
if (instanceManager == null) {
@@ -142,7 +141,10 @@ public class UpdateModuleImpl {
promise.resolve(true);
} catch (Throwable err) {
promise.reject("pushy:"+err.getMessage());
activity.recreate();
final Activity activity = mContext.getCurrentActivity();
if (activity != null) {
activity.recreate();
}
}
} catch (Throwable err) {

View File

@@ -1,7 +1,7 @@
package cn.reactnative.modules.update;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
@@ -176,8 +176,7 @@ public class UpdateModule extends ReactContextBaseJavaModule {
public void run() {
try {
updateContext.switchVersion(hash);
Activity activity = getCurrentActivity();
Application application = activity.getApplication();
final Context application = getReactApplicationContext().getApplicationContext();
ReactInstanceManager instanceManager = updateContext.getCustomReactInstanceManager();
if (instanceManager == null) {
@@ -200,7 +199,10 @@ public class UpdateModule extends ReactContextBaseJavaModule {
instanceManager.recreateReactContextInBackground();
promise.resolve(null);
} catch (Throwable err) {
activity.recreate();
final Activity activity = getCurrentActivity();
if (activity != null) {
activity.recreate();
}
promise.reject(err);
}

BIN
bun.lockb Executable file

Binary file not shown.

View File

@@ -1,6 +1,10 @@
#import "RCTPushy.h"
#import "RCTPushyDownloader.h"
#import "RCTPushyManager.h"
#if __has_include("RCTReloadCommand.h")
#import "RCTReloadCommand.h"
#endif
// Thanks to this guard, we won't import this header when we build for the old architecture.
#ifdef RCT_NEW_ARCH_ENABLED
#import "RCTPushySpec.h"
@@ -75,7 +79,7 @@ RCT_EXPORT_MODULE(RCTPushy);
if (needClearPushyInfo) {
[defaults setObject:nil forKey:keyPushyInfo];
[defaults setObject:@(YES) forKey:KeyPackageUpdatedMarked];
[defaults synchronize];
// ...need clear files later
}
else {
@@ -97,7 +101,7 @@ RCT_EXPORT_MODULE(RCTPushy);
newInfo[paramIsFirstTime] = @(NO);
[defaults setObject:newInfo forKey:keyPushyInfo];
[defaults setObject:@(YES) forKey:keyFirstLoadMarked];
[defaults synchronize];
}
NSString *downloadDir = [RCTPushy downloadDir];
@@ -137,7 +141,7 @@ RCT_EXPORT_MODULE(RCTPushy);
[defaults setObject:nil forKey:keyPushyInfo];
}
[defaults setObject:curVersion forKey:keyRolledBackMarked];
[defaults synchronize];
return lastVersion;
}
@@ -176,7 +180,7 @@ RCT_EXPORT_MODULE(RCTPushy);
[defaults setObject:nil forKey:KeyPackageUpdatedMarked];
[self clearInvalidFiles];
}
[defaults synchronize];
return ret;
}
@@ -196,7 +200,7 @@ RCT_EXPORT_METHOD(setUuid:(NSString *)uuid resolver:(RCTPromiseResolveBlock)res
@try {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:uuid forKey:keyUuid];
[defaults synchronize];
resolve(@true);
}
@catch (NSException *exception) {
@@ -214,7 +218,7 @@ RCT_EXPORT_METHOD(setLocalHashInfo:(NSString *)hash
if (object && [object isKindOfClass:[NSDictionary class]]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:value forKey:[keyHashInfo stringByAppendingString:hash]];
[defaults synchronize];
resolve(@true);
} else {
reject(@"json格式校验报错", nil, nil);
@@ -295,7 +299,7 @@ RCT_EXPORT_METHOD(setNeedUpdate:(NSDictionary *)options
newInfo[paramPackageVersion] = [RCTPushy packageVersion];
[defaults setObject:newInfo forKey:keyPushyInfo];
[defaults synchronize];
resolve(@true);
}else{
reject(@"执行报错", nil, nil);
@@ -311,14 +315,17 @@ RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
if (hash.length) {
[self setNeedUpdate:options resolver:resolve rejecter:reject];
// reload 0.62+
// RCTReloadCommandSetBundleURL([[self class] bundleURL]);
// RCTTriggerReloadCommandListeners(@"pushy reload");
dispatch_async(dispatch_get_main_queue(), ^{
[self.bridge setValue:[[self class] bundleURL] forKey:@"bundleURL"];
[self.bridge reload];
});
#if __has_include("RCTReloadCommand.h")
// reload 0.62+
RCTReloadCommandSetBundleURL([[self class] bundleURL]);
RCTTriggerReloadCommandListeners(@"pushy reload");
#else
// reload in earlier version
dispatch_async(dispatch_get_main_queue(), ^{
[self.bridge setValue:[[self class] bundleURL] forKey:@"bundleURL"];
[self.bridge reload];
});
#endif
resolve(@true);
}else{
reject(@"执行报错", nil, nil);
@@ -329,8 +336,7 @@ RCT_EXPORT_METHOD(reloadUpdate:(NSDictionary *)options
}
}
RCT_EXPORT_METHOD(markSuccess:
resolver:(RCTPromiseResolveBlock)resolve
RCT_EXPORT_METHOD(markSuccess:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
@@ -347,7 +353,7 @@ RCT_EXPORT_METHOD(markSuccess:
[pushyInfo removeObjectForKey:[keyHashInfo stringByAppendingString:lastVersion]];
}
[defaults setObject:pushyInfo forKey:keyPushyInfo];
[defaults synchronize];
// clear other package dir
[self clearInvalidFiles];
@@ -537,7 +543,15 @@ RCT_EXPORT_METHOD(markSuccess:
for(NSString *fileName in list) {
if (![fileName isEqualToString:curVersion]) {
[_fileManager removeFile:[downloadDir stringByAppendingPathComponent:fileName] completionHandler:nil];
NSString *filePath = [downloadDir stringByAppendingPathComponent:fileName];
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
if (error) {
continue;
}
NSDate *modificationDate = [attributes fileModificationDate];
if ([[NSDate date] timeIntervalSinceDate:modificationDate] > 7 * 24 * 60 * 60) {
[_fileManager removeFile:filePath completionHandler:nil];
}
}
}
}
@@ -610,7 +624,7 @@ RCT_EXPORT_METHOD(markSuccess:
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeUpdateSpecJSI>(params);
return std::make_shared<facebook::react::NativePushySpecJSI>(params);
}
#endif

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-update",
"version": "10.11.0",
"version": "10.19.1",
"description": "react-native hot update",
"main": "src/index",
"scripts": {
@@ -45,7 +45,8 @@
},
"homepage": "https://github.com/reactnativecn/react-native-pushy#readme",
"dependencies": {
"nanoid": "^3.3.3"
"nanoid": "^3.3.3",
"react-native-url-polyfill": "^2.0.0"
},
"codegenConfig": {
"name": "RCTPushySpec",
@@ -53,25 +54,26 @@
"jsSrcsDir": "src"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/core": "^7.25.8",
"@react-native/babel-preset": "^0.73.21",
"@react-native/eslint-config": "^0.73.2",
"@react-native/typescript-config": "^0.74.0",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^29.2.1",
"@types/node": "^20.8.9",
"@types/react": "^18.2.46",
"detox": "^20.5.0",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.13",
"@types/node": "^22.7.6",
"@types/react": "^18.3.11",
"detox": "^20.27.3",
"eslint": "^8.57.0",
"eslint-plugin-ft-flow": "^3.0.7",
"firebase-tools": "^11.24.1",
"fs-extra": "^9.1.0",
"firebase-tools": "^13.22.1",
"fs-extra": "^11.2.0",
"jest": "^29.7.0",
"pod-install": "^0.1.37",
"pod-install": "^0.2.2",
"prettier": "^2",
"react": "18.2.0",
"react-native": "0.73",
"ts-jest": "^29.0.3",
"typescript": "^5.3.3"
}
"ts-jest": "^29.2.5",
"typescript": "^5.6.3"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}

View File

@@ -1,5 +1,5 @@
import { CheckResult, PushyOptions, ProgressData, EventType } from './type';
import { log, testUrls } from './utils';
import { emptyObj, joinUrls, log, noop, promiseAny, testUrls } from './utils';
import { EmitterSubscription, Platform } from 'react-native';
import { PermissionsAndroid } from './permissions';
import {
@@ -11,6 +11,7 @@ import {
packageVersion,
rolledBackVersion,
setLocalHashInfo,
isFirstTime,
isRolledBack,
} from './core';
@@ -23,9 +24,6 @@ const defaultServer = {
],
};
const empty = {};
const noop = () => {};
if (Platform.OS === 'web') {
console.warn('react-native-update 不支持 web 端热更,不会执行操作');
}
@@ -45,12 +43,24 @@ export class Pushy {
lastChecking?: number;
lastRespJson?: Promise<any>;
progressHandlers: Record<string, EmitterSubscription> = {};
downloadedHash?: string;
static progressHandlers: Record<string, EmitterSubscription> = {};
static downloadedHash?: string;
marked = false;
applyingUpdate = false;
static apkStatus: 'downloading' | 'downloaded' | null = null;
static marked = false;
static applyingUpdate = false;
version = cInfo.pushy;
loggerPromise = (() => {
let resolve: (value?: unknown) => void = () => {};
const promise = new Promise(res => {
resolve = res;
});
return {
promise,
resolve,
};
})();
constructor(options: PushyOptions) {
if (Platform.OS === 'ios' || Platform.OS === 'android') {
@@ -59,28 +69,28 @@ export class Pushy {
}
}
this.setOptions(options);
if (isRolledBack) {
this.report({
type: 'rollback',
data: {
rolledBackVersion,
},
});
}
}
setOptions = (options: Partial<PushyOptions>) => {
for (const [key, value] of Object.entries(options)) {
if (value !== undefined) {
// @ts-expect-error
this.options[key] = value;
(this.options as any)[key] = value;
if (key === 'logger') {
if (isRolledBack) {
this.report({
type: 'rollback',
data: {
rolledBackVersion,
},
});
}
this.loggerPromise.resolve();
}
}
}
};
report = ({
report = async ({
type,
message = '',
data = {},
@@ -90,6 +100,7 @@ export class Pushy {
data?: Record<string, string | number>;
}) => {
log(type + ' ' + message);
await this.loggerPromise.promise;
const { logger = noop, appKey } = this.options;
logger({
type,
@@ -104,52 +115,56 @@ export class Pushy {
},
});
};
throwIfEnabled = (e: Error) => {
if (this.options.throwError) {
throw e;
}
};
getCheckUrl = (endpoint: string = this.options.server!.main) => {
return `${endpoint}/checkUpdate/${this.options.appKey}`;
};
assertHash = (hash: string) => {
if (!this.downloadedHash) {
static assertHash = (hash: string) => {
if (!Pushy.downloadedHash) {
return;
}
if (hash !== this.downloadedHash) {
log(`use downloaded hash ${this.downloadedHash} first`);
if (hash !== Pushy.downloadedHash) {
log(`use downloaded hash ${Pushy.downloadedHash} first`);
return;
}
return true;
};
markSuccess = () => {
if (this.marked || __DEV__) {
if (Pushy.marked || __DEV__ || !isFirstTime) {
return;
}
this.marked = true;
Pushy.marked = true;
PushyModule.markSuccess();
this.report({ type: 'markSuccess' });
};
switchVersion = (hash: string) => {
switchVersion = async (hash: string) => {
if (__DEV__) {
console.warn(
'您调用了switchVersion方法但是当前是开发环境不会进行任何操作。',
);
return;
}
if (this.assertHash(hash) && !this.applyingUpdate) {
if (Pushy.assertHash(hash) && !Pushy.applyingUpdate) {
log('switchVersion: ' + hash);
this.applyingUpdate = true;
PushyModule.reloadUpdate({ hash });
Pushy.applyingUpdate = true;
return PushyModule.reloadUpdate({ hash });
}
};
switchVersionLater = (hash: string) => {
switchVersionLater = async (hash: string) => {
if (__DEV__) {
console.warn(
'您调用了switchVersionLater方法但是当前是开发环境不会进行任何操作。',
);
return;
}
if (this.assertHash(hash)) {
if (Pushy.assertHash(hash)) {
log('switchVersionLater: ' + hash);
PushyModule.setNeedUpdate({ hash });
return PushyModule.setNeedUpdate({ hash });
}
};
checkUpdate = async (extra?: Record<string, any>) => {
@@ -163,6 +178,13 @@ export class Pushy {
console.warn('web 端不支持热更新检查');
return;
}
if (
this.options.beforeCheckUpdate &&
(await this.options.beforeCheckUpdate()) === false
) {
log('beforeCheckUpdate 返回 false, 忽略检查');
return;
}
const now = Date.now();
if (
this.lastRespJson &&
@@ -172,7 +194,6 @@ export class Pushy {
return await this.lastRespJson;
}
this.lastChecking = now;
this.report({ type: 'checking' });
const fetchBody = {
packageVersion,
hash: currentVersion,
@@ -181,18 +202,24 @@ export class Pushy {
...extra,
};
if (__DEV__) {
// @ts-ignore
delete fetchBody.buildTime;
}
const body = JSON.stringify(fetchBody);
const fetchPayload = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(fetchBody),
body,
};
let resp;
try {
this.report({
type: 'checking',
message: this.options.appKey + ': ' + body,
});
resp = await fetch(this.getCheckUrl(), fetchPayload);
} catch (e: any) {
this.report({
@@ -202,12 +229,16 @@ export class Pushy {
const backupEndpoints = await this.getBackupEndpoints();
if (backupEndpoints) {
try {
resp = await Promise.race(
resp = await promiseAny(
backupEndpoints.map(endpoint =>
fetch(this.getCheckUrl(endpoint), fetchPayload),
),
);
} catch {}
} catch (err: any) {
this.throwIfEnabled(new Error('errorCheckingUseBackup'));
}
} else {
this.throwIfEnabled(new Error('errorCheckingGetBackup'));
}
}
if (!resp) {
@@ -215,17 +246,21 @@ export class Pushy {
type: 'errorChecking',
message: 'Can not connect to update server. Please check your network.',
});
return this.lastRespJson ? await this.lastRespJson : empty;
this.throwIfEnabled(new Error('errorChecking'));
return this.lastRespJson ? await this.lastRespJson : emptyObj;
}
this.lastRespJson = resp.json();
const result: CheckResult = await this.lastRespJson;
log('checking result:', result);
if (resp.status !== 200) {
this.report({
type: 'errorChecking',
message: result.message,
});
this.throwIfEnabled(new Error(result.message));
}
return result;
@@ -237,7 +272,7 @@ export class Pushy {
}
if (server.queryUrls) {
try {
const resp = await Promise.race(
const resp = await promiseAny(
server.queryUrls.map(queryUrl => fetch(queryUrl)),
);
const remoteEndpoints = await resp.json();
@@ -259,16 +294,21 @@ export class Pushy {
) => {
const {
hash,
diffUrl: _diffUrl,
diffUrls,
pdiffUrl: _pdiffUrl,
pdiffUrls,
updateUrl: _updateUrl,
updateUrls,
diff,
pdiff,
full,
paths = [],
name,
description,
description = '',
metaInfo,
} = info;
if (
this.options.beforeDownloadUpdate &&
(await this.options.beforeDownloadUpdate(info)) === false
) {
log('beforeDownloadUpdate 返回 false, 忽略下载');
return;
}
if (!info.update || !hash) {
return;
}
@@ -276,15 +316,15 @@ export class Pushy {
log(`rolledback hash ${rolledBackVersion}, ignored`);
return;
}
if (this.downloadedHash === hash) {
log(`duplicated downloaded hash ${this.downloadedHash}, ignored`);
return this.downloadedHash;
if (Pushy.downloadedHash === hash) {
log(`duplicated downloaded hash ${Pushy.downloadedHash}, ignored`);
return Pushy.downloadedHash;
}
if (this.progressHandlers[hash]) {
if (Pushy.progressHandlers[hash]) {
return;
}
if (onDownloadProgress) {
this.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
Pushy.progressHandlers[hash] = pushyNativeEventEmitter.addListener(
'RCTPushyDownloadProgress',
progressData => {
if (progressData.hash === hash) {
@@ -293,10 +333,10 @@ export class Pushy {
},
);
}
let succeeded = false;
let succeeded = '';
this.report({ type: 'downloading' });
let lastError: any;
const diffUrl = (await testUrls(diffUrls)) || _diffUrl;
const diffUrl = await testUrls(joinUrls(paths, diff));
if (diffUrl) {
log('downloading diff');
try {
@@ -305,17 +345,17 @@ export class Pushy {
hash,
originHash: currentVersion,
});
succeeded = true;
succeeded = 'diff';
} catch (e: any) {
lastError = e;
if (__DEV__) {
succeeded = true;
succeeded = 'diff';
} else {
log(`diff error: ${e.message}, try pdiff`);
}
}
}
const pdiffUrl = (await testUrls(pdiffUrls)) || _pdiffUrl;
const pdiffUrl = await testUrls(joinUrls(paths, pdiff));
if (!succeeded && pdiffUrl) {
log('downloading pdiff');
try {
@@ -323,37 +363,37 @@ export class Pushy {
updateUrl: pdiffUrl,
hash,
});
succeeded = true;
succeeded = 'pdiff';
} catch (e: any) {
lastError = e;
if (__DEV__) {
succeeded = true;
succeeded = 'pdiff';
} else {
log(`pdiff error: ${e.message}, try full patch`);
}
}
}
const updateUrl = (await testUrls(updateUrls)) || _updateUrl;
if (!succeeded && updateUrl) {
const fullUrl = await testUrls(joinUrls(paths, full));
if (!succeeded && fullUrl) {
log('downloading full patch');
try {
await PushyModule.downloadFullUpdate({
updateUrl: updateUrl,
updateUrl: fullUrl,
hash,
});
succeeded = true;
succeeded = 'full';
} catch (e: any) {
lastError = e;
if (__DEV__) {
succeeded = true;
succeeded = 'full';
} else {
log(`full patch error: ${e.message}`);
}
}
}
if (this.progressHandlers[hash]) {
this.progressHandlers[hash].remove();
delete this.progressHandlers[hash];
if (Pushy.progressHandlers[hash]) {
Pushy.progressHandlers[hash].remove();
delete Pushy.progressHandlers[hash];
}
if (__DEV__) {
return hash;
@@ -367,14 +407,19 @@ export class Pushy {
throw lastError;
}
return;
} else {
this.report({
type: 'downloadSuccess',
data: { newVersion: hash, diff: succeeded },
});
}
log('downloaded hash:', hash);
log(`downloaded ${succeeded} hash:`, hash);
setLocalHashInfo(hash, {
name,
description,
metaInfo,
});
this.downloadedHash = hash;
Pushy.downloadedHash = hash;
return hash;
};
downloadAndInstallApk = async (
@@ -384,22 +429,38 @@ export class Pushy {
if (Platform.OS !== 'android') {
return;
}
this.report({ type: 'downloadingApk' });
if (Pushy.apkStatus === 'downloading') {
return;
}
if (Pushy.apkStatus === 'downloaded') {
this.report({ type: 'errorInstallApk' });
this.throwIfEnabled(new Error('errorInstallApk'));
return;
}
if (Platform.Version <= 23) {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
return this.report({ type: 'rejectStoragePermission' });
this.report({ type: 'rejectStoragePermission' });
this.throwIfEnabled(new Error('rejectStoragePermission'));
return;
}
} catch (e: any) {
return this.report({ type: 'errorStoragePermission' });
this.report({ type: 'errorStoragePermission' });
this.throwIfEnabled(e);
return;
}
}
Pushy.apkStatus = 'downloading';
this.report({ type: 'downloadingApk' });
const progressKey = 'downloadingApk';
if (onDownloadProgress) {
this.progressHandlers[progressKey] = pushyNativeEventEmitter.addListener(
if (Pushy.progressHandlers[progressKey]) {
Pushy.progressHandlers[progressKey].remove();
}
Pushy.progressHandlers[progressKey] = pushyNativeEventEmitter.addListener(
'RCTPushyDownloadProgress',
(progressData: ProgressData) => {
if (progressData.hash === progressKey) {
@@ -413,11 +474,14 @@ export class Pushy {
target: 'update.apk',
hash: progressKey,
}).catch(() => {
Pushy.apkStatus = null;
this.report({ type: 'errorDownloadAndInstallApk' });
this.throwIfEnabled(new Error('errorDownloadAndInstallApk'));
});
if (this.progressHandlers[progressKey]) {
this.progressHandlers[progressKey].remove();
delete this.progressHandlers[progressKey];
Pushy.apkStatus = 'downloaded';
if (Pushy.progressHandlers[progressKey]) {
Pushy.progressHandlers[progressKey].remove();
delete Pushy.progressHandlers[progressKey];
}
};
}

View File

@@ -7,8 +7,8 @@ const asyncNoop = () => Promise.resolve();
export const defaultContext = {
checkUpdate: asyncNoop,
switchVersion: noop,
switchVersionLater: noop,
switchVersion: asyncNoop,
switchVersionLater: asyncNoop,
markSuccess: noop,
dismissError: noop,
downloadUpdate: asyncNoop,
@@ -21,11 +21,11 @@ export const defaultContext = {
export const PushyContext = createContext<{
checkUpdate: () => Promise<void>;
switchVersion: () => void;
switchVersionLater: () => void;
switchVersion: () => Promise<void>;
switchVersionLater: () => Promise<void>;
markSuccess: () => void;
dismissError: () => void;
downloadUpdate: () => Promise<void>;
downloadUpdate: () => Promise<boolean | void>;
downloadAndInstallApk: (url: string) => Promise<void>;
getCurrentVersionInfo: () => Promise<{
name?: string;

View File

@@ -4,9 +4,7 @@ const {
version: v,
} = require('react-native/Libraries/Core/ReactNativeVersion');
const RNVersion = `${v.major}.${v.minor}.${v.patch}`;
const isTurboModuleEnabled =
// @ts-expect-error
global.__turboModuleProxy != null;
const isTurboModuleEnabled = (global as any).__turboModuleProxy != null;
export const PushyModule =
Platform.OS === 'web'
@@ -16,26 +14,26 @@ export const PushyModule =
: NativeModules.Pushy;
if (!PushyModule) {
throw new Error('react-native-update模块无法加载请对照安装文档检查配置。');
throw new Error('react-native-update 模块无法加载,请对照安装文档检查配置。');
}
const PushyConstants = isTurboModuleEnabled
? PushyModule.getConstants()
: PushyModule;
export const downloadRootDir = PushyConstants.downloadRootDir;
export const packageVersion = PushyConstants.packageVersion;
export const currentVersion = PushyConstants.currentVersion;
export const isFirstTime = PushyConstants.isFirstTime;
export const rolledBackVersion = PushyConstants.rolledBackVersion;
export const isRolledBack = typeof rolledBackVersion === 'string';
export const downloadRootDir: string = PushyConstants.downloadRootDir;
export const packageVersion: string = PushyConstants.packageVersion;
export const currentVersion: string = PushyConstants.currentVersion;
export const isFirstTime: boolean = PushyConstants.isFirstTime;
export const rolledBackVersion: string = PushyConstants.rolledBackVersion;
export const isRolledBack: boolean = typeof rolledBackVersion === 'string';
export const buildTime = PushyConstants.buildTime;
export const buildTime: string = PushyConstants.buildTime;
let uuid = PushyConstants.uuid;
if (Platform.OS === 'android' && !PushyConstants.isUsingBundleUrl) {
throw new Error(
'react-native-update模块无法加载请对照文档检查Bundle URL的配置',
'react-native-update 模块无法加载,请对照文档检查 Bundle URL 的配置',
);
}

77
src/isInRollout.ts Normal file
View File

@@ -0,0 +1,77 @@
/* eslint-disable no-fallthrough */
import { cInfo } from './core';
/* eslint-disable no-bitwise */
function murmurhash3_32_gc(key: string, seed = 0) {
let remainder, bytes, h1, h1b, c1, c2, k1, i;
remainder = key.length & 3; // key.length % 4
bytes = key.length - remainder;
h1 = seed;
c1 = 0xcc9e2d51;
c2 = 0x1b873593;
i = 0;
while (i < bytes) {
k1 =
(key.charCodeAt(i) & 0xff) |
((key.charCodeAt(++i) & 0xff) << 8) |
((key.charCodeAt(++i) & 0xff) << 16) |
((key.charCodeAt(++i) & 0xff) << 24);
++i;
((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 =
((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> 19);
h1b =
((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
}
k1 = 0;
switch (remainder) {
case 3:
k1 ^= (key.charCodeAt(i + 2) & 0xff) << 16;
case 2:
k1 ^= (key.charCodeAt(i + 1) & 0xff) << 8;
case 1:
k1 ^= key.charCodeAt(i) & 0xff;
k1 =
((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) &
0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 =
((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) &
0xffffffff;
h1 ^= k1;
}
h1 ^= key.length;
h1 ^= h1 >>> 16;
h1 =
((h1 & 0xffff) * 0x85ebca6b +
((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) &
0xffffffff;
h1 ^= h1 >>> 13;
h1 =
((h1 & 0xffff) * 0xc2b2ae35 +
((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) &
0xffffffff;
h1 ^= h1 >>> 16;
return h1 >>> 0;
}
const intForUUID = murmurhash3_32_gc(cInfo.uuid);
export function isInRollout(rollout: number) {
return intForUUID % 100 < rollout;
}

View File

@@ -13,14 +13,12 @@ import {
Linking,
} from 'react-native';
import { Pushy } from './client';
import {
currentVersion,
isFirstTime,
packageVersion,
getCurrentVersionInfo,
} from './core';
import { currentVersion, packageVersion, getCurrentVersionInfo } from './core';
import { CheckResult, ProgressData, PushyTestPayload } from './type';
import { PushyContext } from './context';
import { URL } from 'react-native-url-polyfill';
import { isInRollout } from './isInRollout';
import { log } from './utils';
export const PushyProvider = ({
client,
@@ -72,18 +70,18 @@ export const PushyProvider = ({
);
const switchVersion = useCallback(
(info: CheckResult | undefined = updateInfoRef.current) => {
async (info: CheckResult | undefined = updateInfoRef.current) => {
if (info && info.hash) {
client.switchVersion(info.hash);
return client.switchVersion(info.hash);
}
},
[client],
);
const switchVersionLater = useCallback(
(info: CheckResult | undefined = updateInfoRef.current) => {
async (info: CheckResult | undefined = updateInfoRef.current) => {
if (info && info.hash) {
client.switchVersionLater(info.hash);
return client.switchVersionLater(info.hash);
}
},
[client],
@@ -92,18 +90,20 @@ export const PushyProvider = ({
const downloadUpdate = useCallback(
async (info: CheckResult | undefined = updateInfoRef.current) => {
if (!info || !info.update) {
return;
return false;
}
try {
const hash = await client.downloadUpdate(info, setProgress);
if (!hash) {
return;
return false;
}
stateListener.current && stateListener.current.remove();
if (options.updateStrategy === 'silentAndNow') {
return client.switchVersion(hash);
client.switchVersion(hash);
return true;
} else if (options.updateStrategy === 'silentAndLater') {
return client.switchVersionLater(hash);
client.switchVersionLater(hash);
return true;
}
alertUpdate('提示', '下载完毕,是否立即更新?', [
{
@@ -121,10 +121,12 @@ export const PushyProvider = ({
},
},
]);
return true;
} catch (e: any) {
setLastError(e);
alertError('更新失败', e.message);
throwErrorIfEnabled(e);
return false;
}
},
[
@@ -146,7 +148,7 @@ export const PushyProvider = ({
);
const checkUpdate = useCallback(
async (extra?: Record<string, any>) => {
async ({ extra }: { extra?: Record<string, any> } | undefined = {}) => {
const now = Date.now();
if (lastChecking.current && now - lastChecking.current < 1000) {
return;
@@ -164,6 +166,15 @@ export const PushyProvider = ({
if (!info) {
return;
}
const rollout = info.config?.rollout?.[packageVersion];
if (rollout) {
if (!isInRollout(rollout)) {
log(`not in ${rollout}% rollout, ignored`);
return;
}
log(`in ${rollout}% rollout, continue`);
}
info.description = info.description ?? '';
updateInfoRef.current = info;
setUpdateInfo(info);
if (info.expired) {
@@ -195,7 +206,8 @@ export const PushyProvider = ({
options.updateStrategy === 'silentAndNow' ||
options.updateStrategy === 'silentAndLater'
) {
return downloadUpdate(info);
downloadUpdate(info);
return;
}
alertUpdate(
'提示',
@@ -234,7 +246,7 @@ export const PushyProvider = ({
return;
}
const { checkStrategy, dismissErrorAfter, autoMarkSuccess } = options;
if (isFirstTime && autoMarkSuccess) {
if (autoMarkSuccess) {
markSuccess();
}
if (checkStrategy === 'both' || checkStrategy === 'onAppResume') {
@@ -271,7 +283,7 @@ export const PushyProvider = ({
Alert.alert(type, JSON.stringify(data));
};
if (payload.type === '__rnPushyVersionHash') {
checkUpdate({ toHash: payload.data }).then(() => {
checkUpdate({ extra: { toHash: payload.data } }).then(() => {
if (updateInfoRef.current && updateInfoRef.current.upToDate) {
Alert.alert(
'提示',
@@ -289,10 +301,9 @@ export const PushyProvider = ({
);
const parseTestQrCode = useCallback(
(code: string) => {
let payload: PushyTestPayload;
(code: string | PushyTestPayload) => {
try {
payload = JSON.parse(code);
const payload = typeof code === 'string' ? JSON.parse(code) : code;
return parseTestPayload(payload);
} catch {
return false;
@@ -306,7 +317,7 @@ export const PushyProvider = ({
if (!url) {
return;
}
const params = new URLSearchParams(url);
const params = new URL(url).searchParams;
const payload = {
type: params.get('type'),
data: params.get('data'),

View File

@@ -7,12 +7,16 @@ export interface CheckResult {
hash?: string;
description?: string;
metaInfo?: string;
pdiffUrl?: string;
pdiffUrls?: string[];
diffUrl?: string;
diffUrls?: string[];
updateUrl?: string;
updateUrls?: string[];
config?: {
rollout?: {
[packageVersion: string]: number;
};
[key: string]: any;
};
pdiff?: string;
diff?: string;
full?: string;
paths?: string[];
paused?: 'app' | 'package';
message?: string;
}
@@ -28,12 +32,14 @@ export type EventType =
| 'errorChecking'
| 'checking'
| 'downloading'
| 'downloadSuccess'
| 'errorUpdate'
| 'markSuccess'
| 'downloadingApk'
| 'rejectStoragePermission'
| 'errorStoragePermission'
| 'errorDownloadAndInstallApk';
| 'errorDownloadAndInstallApk'
| 'errorInstallApk';
export interface EventData {
currentVersion: string;
@@ -44,7 +50,7 @@ export interface EventData {
uuid: string;
};
packageVersion: string;
buildTime: number;
buildTime: string;
message?: string;
rolledBackVersion?: string;
newVersion?: string;
@@ -80,6 +86,8 @@ export interface PushyOptions {
dismissErrorAfter?: number;
debug?: boolean;
throwError?: boolean;
beforeCheckUpdate?: () => Promise<boolean>;
beforeDownloadUpdate?: (info: CheckResult) => Promise<boolean>;
}
export interface PushyTestPayload {

View File

@@ -4,7 +4,25 @@ export function log(...args: any[]) {
console.log('pushy: ', ...args);
}
const noop = () => {};
export function promiseAny<T>(promises: Promise<T>[]) {
return new Promise<T>((resolve, reject) => {
let count = 0;
promises.forEach(promise => {
Promise.resolve(promise)
.then(resolve)
.catch(() => {
count++;
if (count === promises.length) {
reject(new Error('All promises were rejected'));
}
});
});
});
}
export const emptyObj = {};
export const noop = () => {};
class EmptyModule {
constructor() {
return new Proxy(this, {
@@ -29,11 +47,15 @@ const ping =
new Promise(r => setTimeout(() => r(null), 2000)),
]);
const canUseGoogle = ping('https://www.google.com');
export function joinUrls(paths: string[], fileName?: string) {
if (fileName) {
return paths.map(path => 'https://' + path + '/' + fileName);
}
}
export const testUrls = async (urls?: string[]) => {
if (!urls?.length || (await canUseGoogle)) {
if (!urls?.length) {
return null;
}
return Promise.race(urls.map(ping)).catch(() => null);
return promiseAny(urls.map(ping)).catch(() => null);
};

10639
yarn.lock

File diff suppressed because it is too large Load Diff