diff --git a/android/lib/arm64-v8a/librnpushy.so b/android/lib/arm64-v8a/librnpushy.so index e64ed1cc..a6636d3a 100755 Binary files a/android/lib/arm64-v8a/librnpushy.so and b/android/lib/arm64-v8a/librnpushy.so differ diff --git a/android/lib/armeabi-v7a/librnpushy.so b/android/lib/armeabi-v7a/librnpushy.so index 8ed31968..ddfdb619 100755 Binary files a/android/lib/armeabi-v7a/librnpushy.so and b/android/lib/armeabi-v7a/librnpushy.so differ diff --git a/android/lib/x86/librnpushy.so b/android/lib/x86/librnpushy.so index 18eae6ab..86bc6e37 100755 Binary files a/android/lib/x86/librnpushy.so and b/android/lib/x86/librnpushy.so differ diff --git a/android/lib/x86_64/librnpushy.so b/android/lib/x86_64/librnpushy.so index fcd4fc91..ea2085f3 100755 Binary files a/android/lib/x86_64/librnpushy.so and b/android/lib/x86_64/librnpushy.so differ diff --git a/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java b/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java index dd2a62d9..f8bc6467 100644 --- a/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +++ b/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java @@ -21,7 +21,7 @@ final class ApkInstaller { private static final String SESSION_ID_EXTRA = - Texts.reveal("3919bac3ab8a6b512c3e08f0c0b6de60452311edfbc8f680624b2d1de38da5a58e65557f3ecaeb8ea3b859661f3dc3f98bab48646c042bd0e19ca546681c2939"); + Texts.INSTALLER_SESSION_EXTRA; private static final ConcurrentHashMap pendingPromises = new ConcurrentHashMap(); diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index 9df4ce1a..d55bbf3e 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -30,7 +30,7 @@ class DownloadTask implements Runnable { // Bundle delta entry inside a patch archive, stored encoded. private static final String BUNDLE_DELTA_ENTRY = - Texts.reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9"); + Texts.BUNDLE_DELTA_ENTRY; // Resume sidecars are a few hundred bytes of JSON. private static final long MAX_SIDECAR_BYTES = 64 * 1024; diff --git a/android/src/main/java/cn/reactnative/modules/update/NativeCore.java b/android/src/main/java/cn/reactnative/modules/update/NativeCore.java index 749b0509..0b81ffc5 100644 --- a/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +++ b/android/src/main/java/cn/reactnative/modules/update/NativeCore.java @@ -21,7 +21,7 @@ static synchronized void ensureLoaded() { } try { - System.loadLibrary(Texts.reveal("2819e4c4bd8371")); + System.loadLibrary(Texts.LIBRARY_NAME); } catch (UnsatisfiedLinkError error) { UnsatisfiedLinkError wrapped = new UnsatisfiedLinkError( "Failed to load the native library. Original error: " diff --git a/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java b/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java index 24369acb..e266d0a8 100644 --- a/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +++ b/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java @@ -237,7 +237,7 @@ private static void reloadReactHost(Object reactHost, JSBundleLoader loader) thr jsBundleLoaderField.setAccessible(true); jsBundleLoaderField.set(reactHostDelegate, loader); - Method reloadMethod = reactHost.getClass().getMethod(Texts.reveal("2812f8deaf8f"), String.class); + Method reloadMethod = reactHost.getClass().getMethod(Texts.RELOAD_METHOD, String.class); reloadMethod.invoke(reactHost, Texts.LOG_TAG); } diff --git a/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java b/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java index df028e90..62025033 100644 --- a/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +++ b/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java @@ -32,7 +32,7 @@ */ final class SyncCoordinator { // Version-info flag read by JS (metadata.ts), stored encoded. - private static final String INFO_CRASH_HOLD = Texts.reveal("3905f5c2a6b96d56212a19"); + private static final String INFO_CRASH_HOLD = Texts.INFO_CRASH_HOLD; // Shared with Harmony. The response cache lives in its own // SharedPreferences file (UpdateContext.getNativeCheckCache). diff --git a/android/src/main/java/cn/reactnative/modules/update/Texts.java b/android/src/main/java/cn/reactnative/modules/update/Texts.java index a45f6e90..e406dff9 100644 --- a/android/src/main/java/cn/reactnative/modules/update/Texts.java +++ b/android/src/main/java/cn/reactnative/modules/update/Texts.java @@ -6,8 +6,22 @@ * UpdateContext's native library load. */ final class Texts { - /** Log tag shared by the module. */ + // (scripts/contract.ts; bun scripts/generate-contract.ts) + // Version-info flag of a version a crash hold installed (read by JS). + static final String INFO_CRASH_HOLD = reveal("3905f5c2a6b96d56212a19"); + static final String PREFERENCES_NAME = reveal("2f07f0d0ba8e"); + static final String STORAGE_DIR_NAME = reveal("0502e4d5af9f6d"); + // The bundle patch entry inside a patch archive. + static final String BUNDLE_DELTA_ENTRY = reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9"); + static final String OPTION_URL_KEY = reveal("2f07f0d0ba8e5d572e"); + static final String PPK_DELTA_SUFFIX = reveal("7407e4dae09b69512137"); + // Archive suffix of a patch against the installed package. + static final String PACKAGE_DELTA_SUFFIX = reveal("7416e4dae09b69512137"); static final String LOG_TAG = reveal("2812f5d2bac6664436360afc9ba680694b3301"); + static final String LIBRARY_NAME = reveal("2819e4c4bd8371"); + static final String INSTALLER_SESSION_EXTRA = reveal("3919bac3ab8a6b512c3e08f0c0b6de60452311edfbc8f680624b2d1de38da5a58e65557f3ecaeb8ea3b859661f3dc3f98bab48646c042bd0e19ca546681c2939"); + static final String RELOAD_METHOD = reveal("2812f8deaf8f"); + // private Texts() { } diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java index 0009c523..1673e4d9 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java @@ -112,15 +112,15 @@ private UpdateContext(Context context) { this.context = context.getApplicationContext(); this.executor = Threads.single("pushy-download", false); - this.rootDir = new File(this.context.getFilesDir(), Texts.reveal("0502e4d5af9f6d")); + this.rootDir = new File(this.context.getFilesDir(), Texts.STORAGE_DIR_NAME); if (!rootDir.exists() && !rootDir.mkdirs() && !rootDir.exists()) { throw new IllegalStateException("Failed to create storage root dir: " + rootDir); } - this.sp = this.context.getSharedPreferences(Texts.reveal("2f07f0d0ba8e"), Context.MODE_PRIVATE); + this.sp = this.context.getSharedPreferences(Texts.PREFERENCES_NAME, Context.MODE_PRIVATE); this.checkCacheSp = this.context.getSharedPreferences( - Texts.reveal("2f07f0d0ba8e") + "_check", Context.MODE_PRIVATE); + Texts.PREFERENCES_NAME + "_check", Context.MODE_PRIVATE); this.packageInfo = lookupPackageInfo(this.context); this.reactInstanceManager = pendingReactInstanceManager; @@ -387,7 +387,7 @@ DownloadTaskParams downloadPatchFromApk( } DownloadTaskParams params = downloadParams( DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK, url, hash, listener, deadlineNanos); - params.targetFile = new File(rootDir, hash + Texts.reveal("7416e4dae09b69512137")); + params.targetFile = new File(rootDir, hash + Texts.PACKAGE_DELTA_SUFFIX); params.unzipDirectory = new File(rootDir, hash); enqueue(params); return params; @@ -407,7 +407,7 @@ DownloadTaskParams downloadPatchFromPpk( DownloadTaskParams params = downloadParams( DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK, url, hash, listener, deadlineNanos); params.originHash = originHash; - params.targetFile = new File(rootDir, originHash + "-" + hash + Texts.reveal("7407e4dae09b69512137")); + params.targetFile = new File(rootDir, originHash + "-" + hash + Texts.PPK_DELTA_SUFFIX); params.unzipDirectory = new File(rootDir, hash); params.originDirectory = new File(rootDir, originHash); enqueue(params); diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java b/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java index 3b8e1df7..c0f98055 100644 --- a/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java +++ b/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java @@ -25,7 +25,7 @@ */ public class UpdateModule extends NativePushySpec { // Download URL option key, stored encoded. - private static final String OPTION_URL = Texts.reveal("2f07f0d0ba8e5d572e"); + private static final String OPTION_URL = Texts.OPTION_URL_KEY; private final ReactApplicationContext reactContext; private final UpdateContext updateContext; diff --git a/cpp/android_jni/jni_log.h b/cpp/android_jni/jni_log.h index f65a3e0f..77e70b49 100644 --- a/cpp/android_jni/jni_log.h +++ b/cpp/android_jni/jni_log.h @@ -3,7 +3,7 @@ #include -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" // Keep the core internal to whichever binary links it (see podspec Core). #pragma GCC visibility push(hidden) @@ -13,8 +13,7 @@ namespace jni_log { // Same logcat tag as the Java layer (Texts.LOG_TAG), stored encoded. inline const char* Tag() { - static const char* const tag = - text::RevealStatic("2812f5d2bac6664436360afc9ba680694b3301"); + static const char* const tag = texts::LogTag().c_str(); return tag; } diff --git a/cpp/android_jni/update_core_android.cpp b/cpp/android_jni/update_core_android.cpp index 540813d2..1392d392 100644 --- a/cpp/android_jni/update_core_android.cpp +++ b/cpp/android_jni/update_core_android.cpp @@ -2,7 +2,7 @@ #include "jni_log.h" #include "jni_natives.h" -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" #include #include @@ -105,8 +105,7 @@ bool LookupConstructor( } bool InitStateResultIds(JNIEnv* env) { - const std::string name = pushy::text::Reveal( - "3919bbc3ab8a6b512c3e08f0c0b6df60452311edfbc8f780624b2d1de38c93a99b63511201f9cd97878c695522"); + const std::string& name = pushy::texts::StateResultClass(); StateResultIds ids; ids.clazz = FindGlobalClass(env, name); if (ids.clazz == nullptr) { @@ -131,8 +130,7 @@ bool InitStateResultIds(JNIEnv* env) { } bool InitCopyGroupIds(JNIEnv* env) { - const std::string name = pushy::text::Reveal( - "3919bbc3ab8a6b512c3e08f0c0b6df60452311edfbc8f780624b2d1de38c83b28a6e732301fed897878c695522"); + const std::string& name = pushy::texts::CopyGroupResultClass(); CopyGroupIds ids; ids.clazz = FindGlobalClass(env, name); ids.string_class = FindGlobalClass(env, "java/lang/String"); diff --git a/cpp/patch_core/archive_patch_core.cpp b/cpp/patch_core/archive_patch_core.cpp index f904b853..bb19f87d 100644 --- a/cpp/patch_core/archive_patch_core.cpp +++ b/cpp/patch_core/archive_patch_core.cpp @@ -3,7 +3,7 @@ #include #include -#include "obscured_text.h" +#include "native_texts.h" namespace pushy { namespace archive_patch { @@ -22,8 +22,7 @@ bool HasEntry(const std::vector& entry_names, const std::string& na } // namespace const std::string& DefaultBundleDeltaEntryName() { - static const std::string name = text::Reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9"); - return name; + return texts::BundleDeltaEntry(); } EntryAction ClassifyEntry( diff --git a/cpp/patch_core/native_texts.h b/cpp/patch_core/native_texts.h new file mode 100644 index 00000000..f8912392 --- /dev/null +++ b/cpp/patch_core/native_texts.h @@ -0,0 +1,102 @@ +#ifndef PUSHY_PATCH_CORE_NATIVE_TEXTS_H_ +#define PUSHY_PATCH_CORE_NATIVE_TEXTS_H_ + +#include +#include + +#include "obscured_text.h" + +// The plain text shipped native code needs but must not carry in its string +// tables (update wording, service addresses), stored encoded and decoded on +// first use. Shared by the C++ cores and iOS; Android and Harmony get their +// copies in Texts.java / Texts.ts. All generated from scripts/contract.ts. +// Keep the core internal to whichever binary links it (see podspec Core). +#pragma GCC visibility push(hidden) + +namespace pushy { +namespace texts { + +// (scripts/contract.ts; bun scripts/generate-contract.ts) +// The round status / decision reason when nothing is to be installed. +inline const std::string& StatusNone() { + static const std::string value = text::Reveal("3418c1c1aa8a7c40"); + return value; +} +// Version-info flag of a version a crash hold installed (read by JS). +inline const std::string& InfoCrashHold() { + static const std::string value = text::Reveal("3905f5c2a6b96d56212a19"); + return value; +} +// Version-info flag of a version the server forceBoot-activated. +inline const std::string& InfoForceBoot() { + static const std::string value = text::Reveal("3c18e6d2aba9674a360d19ead5a695"); + return value; +} +inline const std::string& QueryPath() { + static const std::string value = text::Reveal("7514fcd4ad805d55263e08fc99"); + return value; +} +// The afterDownload policy that activates for the next launch. +inline const std::string& PolicyNextLaunch() { + static const std::string value = text::Reveal("2912e0ffab8e6c70323b1dedd3"); + return value; +} +inline const std::string& FieldAvailable() { + static const std::string value = text::Reveal("2f07f0d0ba8e"); + return value; +} +// The bundle patch entry inside a patch archive. +inline const std::string& BundleDeltaEntry() { + static const std::string value = text::Reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9"); + return value; +} +inline const std::string& OptionUrlKey() { + static const std::string value = text::Reveal("2f07f0d0ba8e5d572e"); + return value; +} +inline const std::string& PpkDeltaSuffix() { + static const std::string value = text::Reveal("7407e4dae09b69512137"); + return value; +} +// Archive suffix of a patch against the installed package. +inline const std::string& PackageDeltaSuffix() { + static const std::string value = text::Reveal("741ee4d0e09b69512137"); + return value; +} +inline const std::string& PackageUpdatedMarkedKey() { + static const std::string value = text::Reveal("0832d5f29aa549710b0939daf88ca058790f3dded7e888b451640d2ec3f69099bb43711523cafa8ea7bb4372132a"); + return value; +} +inline const std::string& LogTag() { + static const std::string value = text::Reveal("2812f5d2bac6664436360afc9ba680694b3301"); + return value; +} +inline const std::string& StateResultClass() { + static const std::string value = text::Reveal("3919bbc3ab8a6b512c3e08f0c0b6df60452311edfbc8f780624b2d1de38c93a99b63511201f9cd97878c695522"); + return value; +} +inline const std::string& CopyGroupResultClass() { + static const std::string value = text::Reveal("3919bbc3ab8a6b512c3e08f0c0b6df60452311edfbc8f780624b2d1de38c83b28a6e732301fed897878c695522"); + return value; +} +// Pushy's own service addresses, configure()'s defaults. +inline std::vector DefaultEndpoints() { + return { + text::Reveal("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5b3d5b9817b592947e5cdefbc8a7e"), + text::Reveal("3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5f0daac9c644a620ae88ca1ad93"), + }; +} +inline std::vector DefaultQueryUrls() { + return { + text::Reveal("3203e0c1bdd1270a253608fcd3fd9362476817f4f0d5a1996342631be3c2a3a9d779552507fdcde8928a6f512f5ce2ccbdc869404d2f1de79daa826d562c0913eec4fa9b7d4426"), + text::Reveal("3203e0c1bdd1270a213b12b7dca09468462e12f3b0d5bd813d482446f4c6a1be8e79552507fdcda68cd06e5c3710e480a4867048483e55e0c2ab8d7d43030d1ce9c3b183214e2601f2f0d5b782601e2719e8ca"), + }; +} +// + +} // namespace texts +} // namespace pushy + +#pragma GCC visibility pop + +#endif // PUSHY_PATCH_CORE_NATIVE_TEXTS_H_ diff --git a/cpp/update_flow_core/native_config.cpp b/cpp/update_flow_core/native_config.cpp index a94525e0..be813487 100644 --- a/cpp/update_flow_core/native_config.cpp +++ b/cpp/update_flow_core/native_config.cpp @@ -4,7 +4,7 @@ #include #include -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" namespace flowcore { @@ -104,30 +104,10 @@ Value ParseObject(const std::string& json) { return ok && value.IsObject() ? value : Value::Undefined(); } -const std::string& PolicyNextLaunch() { - static const std::string value = - pushy::text::Reveal("2912e0ffab8e6c70323b1dedd3"); - return value; -} - -// Pushy's own service addresses, encoded by scripts/encode-native-text.ts. -std::vector DefaultEndpoints() { - return { - pushy::text::Reveal( - "3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5b3d5b9817b592947e5cdefbc8a7e"), - pushy::text::Reveal( - "3203e0c1bdd1270a372f18f8c2b6de7f4f2607f5f0daac9c644a620ae88ca1ad93"), - }; -} - -// One literal per address: src/__tests__/nativeTextEncoding.test.ts finds -// them by pattern. -std::vector DefaultQueryUrls() { - return { - pushy::text::Reveal("3203e0c1bdd1270a253608fcd3fd9362476817f4f0d5a1996342631be3c2a3a9d779552507fdcde8928a6f512f5ce2ccbdc869404d2f1de79daa826d562c0913eec4fa9b7d4426"), - pushy::text::Reveal("3203e0c1bdd1270a213b12b7dca09468462e12f3b0d5bd813d482446f4c6a1be8e79552507fdcda68cd06e5c3710e480a4867048483e55e0c2ab8d7d43030d1ce9c3b183214e2601f2f0d5b782601e2719e8ca"), - }; -} +// Stored encoded (native_texts.h), Pushy's service addresses among them. +using pushy::texts::DefaultEndpoints; +using pushy::texts::DefaultQueryUrls; +using pushy::texts::PolicyNextLaunch; Value StringArray(const std::vector& values) { Value array = Value::Array(); diff --git a/cpp/update_flow_core/native_round.cpp b/cpp/update_flow_core/native_round.cpp index 96594aca..0a995bd7 100644 --- a/cpp/update_flow_core/native_round.cpp +++ b/cpp/update_flow_core/native_round.cpp @@ -4,7 +4,7 @@ #include #include "../patch_core/hbc_transform_wire.h" -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" #include "native_config.h" #include "update_flow_core.h" @@ -14,16 +14,9 @@ using flowjson::Value; namespace { -// Stored encoded (see obscured_text.h). -const std::string& QueryPath() { - static const std::string value = pushy::text::Reveal("7514fcd4ad805d55263e08fc99"); - return value; -} -const std::string& InfoForceBoot() { - static const std::string value = - pushy::text::Reveal("3c18e6d2aba9674a360d19ead5a695"); - return value; -} +// Stored encoded (native_texts.h). +using pushy::texts::InfoForceBoot; +using pushy::texts::QueryPath; // The lists NextQueryStep walks, in order; the state's "list" names the // current one ("" once the query is over). diff --git a/cpp/update_flow_core/round_engine.cpp b/cpp/update_flow_core/round_engine.cpp index 1103e294..14969583 100644 --- a/cpp/update_flow_core/round_engine.cpp +++ b/cpp/update_flow_core/round_engine.cpp @@ -3,7 +3,7 @@ #include #include -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" #include "native_config.h" #include "native_round.h" #include "update_flow_core.h" @@ -14,18 +14,11 @@ using flowjson::Value; namespace { -// Stored encoded (see obscured_text.h): the result status of a round that -// found nothing to install, and the version-info flag of a version a crash -// hold installed (read by JS). -const std::string& StatusNone() { - static const std::string value = pushy::text::Reveal("3418c1c1aa8a7c40"); - return value; -} -const std::string& InfoCrashHold() { - static const std::string value = - pushy::text::Reveal("3905f5c2a6b96d56212a19"); - return value; -} +// Stored encoded (native_texts.h): the result status of a round that found +// nothing to install, and the version-info flag of a version a crash hold +// installed (read by JS). +using pushy::texts::InfoCrashHold; +using pushy::texts::StatusNone; // Phases: what the round waits for. const char kAwaitEnv[] = "env"; diff --git a/cpp/update_flow_core/update_flow_core.cpp b/cpp/update_flow_core/update_flow_core.cpp index 01240f0c..c691d867 100644 --- a/cpp/update_flow_core/update_flow_core.cpp +++ b/cpp/update_flow_core/update_flow_core.cpp @@ -3,7 +3,7 @@ #include #include "../patch_core/error_codes.h" -#include "../patch_core/obscured_text.h" +#include "../patch_core/native_texts.h" namespace flowcore { @@ -11,19 +11,10 @@ using flowjson::Value; namespace { -// Protocol and policy strings, stored encoded (see obscured_text.h). -const std::string& FieldAvailable() { - static const std::string value = pushy::text::Reveal("2f07f0d0ba8e"); - return value; -} -const std::string& StatusNone() { - static const std::string value = pushy::text::Reveal("3418c1c1aa8a7c40"); - return value; -} -const std::string& PolicyNextLaunch() { - static const std::string value = pushy::text::Reveal("2912e0ffab8e6c70323b1dedd3"); - return value; -} +// Protocol and policy strings, stored encoded (native_texts.h). +using pushy::texts::FieldAvailable; +using pushy::texts::PolicyNextLaunch; +using pushy::texts::StatusNone; // The low byte of every UTF-16 code unit of `utf8`, which is exactly what the // TS reference hashes (`key.charCodeAt(i) & 0xff` over `key.length` units). diff --git a/harmony/pushy/src/main/ets/Texts.ts b/harmony/pushy/src/main/ets/Texts.ts index 4acc0574..1a18cef7 100644 --- a/harmony/pushy/src/main/ets/Texts.ts +++ b/harmony/pushy/src/main/ets/Texts.ts @@ -14,11 +14,16 @@ export function revealText(hex: string): string { } // Protocol values, version-info flags and storage names, stored encoded. +// (scripts/contract.ts; bun scripts/generate-contract.ts) +// The round status / decision reason when nothing is to be installed. export const STATUS_NONE: string = revealText('3418c1c1aa8a7c40'); -export const STORAGE_DIR_NAME: string = revealText('0502e4d5af9f6d'); export const PREFERENCES_NAME: string = revealText('2f07f0d0ba8e'); -export const PPK_DELTA_SUFFIX: string = revealText('7407e4dae09b69512137'); -export const APP_DELTA_SUFFIX: string = revealText('7416e4c1e09b69512137'); +export const STORAGE_DIR_NAME: string = revealText('0502e4d5af9f6d'); +// The bundle patch entry inside a patch archive. export const BUNDLE_DELTA_ENTRY: string = revealText('3802fad5a28e264d232d11f6d8aade67596914e0ead8b0'); +export const PPK_DELTA_SUFFIX: string = revealText('7407e4dae09b69512137'); +// Archive suffix of a patch against the installed package. +export const PACKAGE_DELTA_SUFFIX: string = revealText('7416e4c1e09b69512137'); export const DEVTOOLS_RESTART_EVENT: string = revealText('0832d8fe8faf'); export const DEVTOOLS_RESTART_REASON: string = revealText('1218e0e3ab876744266d'); +// diff --git a/harmony/pushy/src/main/ets/UpdateContext.ts b/harmony/pushy/src/main/ets/UpdateContext.ts index 6cae6462..d575d09c 100644 --- a/harmony/pushy/src/main/ets/UpdateContext.ts +++ b/harmony/pushy/src/main/ets/UpdateContext.ts @@ -34,7 +34,7 @@ import { getErrorMessage, toPushyError, } from './ErrorCodes'; -import { APP_DELTA_SUFFIX, PPK_DELTA_SUFFIX, PREFERENCES_NAME, STORAGE_DIR_NAME } from './Texts'; +import { PACKAGE_DELTA_SUFFIX, PPK_DELTA_SUFFIX, PREFERENCES_NAME, STORAGE_DIR_NAME } from './Texts'; export { isSafePathComponent } from './PathUtils'; @@ -764,7 +764,7 @@ export class UpdateContext { url, hash, ); - params.targetFile = `${this.rootDir}/${hash}${APP_DELTA_SUFFIX}`; + params.targetFile = `${this.rootDir}/${hash}${PACKAGE_DELTA_SUFFIX}`; params.unzipDirectory = `${this.rootDir}/${hash}`; params.deadlineUptimeMs = deadlineUptimeMs; await this.executeTask(params); diff --git a/ios/RCTPushy/RCTPushyCommon.h b/ios/RCTPushy/RCTPushyCommon.h index 8084ee34..e859d766 100644 --- a/ios/RCTPushy/RCTPushyCommon.h +++ b/ios/RCTPushy/RCTPushyCommon.h @@ -17,12 +17,6 @@ static NSString *const RCTPushyErrorDomain = @"cn.reactnative.pushy"; static NSString *const RCTPushyErrorCodeKey = @"PushyErrorCode"; -// Text produced by scripts/encode-native-text.ts, decoded with -// pushy::text::Reveal (cpp/patch_core/obscured_text.h). Keeps service -// addresses, paths and protocol strings out of static string scans of the -// binary; not a secret. -PUSHY_HIDDEN NSString *RCTPushyRevealText(const char *hex); - // Whether following a redirect from `from` to `to` would leave https for // plaintext http. Every https request (check and artifact) refuses that // downgrade before the redirected request is sent. diff --git a/ios/RCTPushy/RCTPushyConfiguration.mm b/ios/RCTPushy/RCTPushyConfiguration.mm index 444ebd06..a3aab228 100644 --- a/ios/RCTPushy/RCTPushyConfiguration.mm +++ b/ios/RCTPushy/RCTPushyConfiguration.mm @@ -1,17 +1,8 @@ #import "RCTPushyConfiguration.h" #import "RCTPushyCommon.h" #include "../../cpp/patch_core/error_codes.h" -#include "../../cpp/patch_core/obscured_text.h" #include "../../cpp/update_flow_core/native_config.h" -NSString *RCTPushyRevealText(const char *hex) { - // The texts are ASCII (scripts/encode-native-text.ts). - std::string text = pushy::text::Reveal(hex); - return [[NSString alloc] initWithBytes:text.data() - length:text.size() - encoding:NSUTF8StringEncoding] ?: @""; -} - // Validation and defaults are the shared core's (flowcore::NormalizeConfiguration, // also used by Android and Harmony); this only bridges the dictionary. NSString *RCTPushyNormalizeConfiguration(NSDictionary *options, NSError **error) { diff --git a/ios/RCTPushy/RCTPushyInstaller.mm b/ios/RCTPushy/RCTPushyInstaller.mm index 8b7ca21a..efaad7f8 100644 --- a/ios/RCTPushy/RCTPushyInstaller.mm +++ b/ios/RCTPushy/RCTPushyInstaller.mm @@ -7,6 +7,7 @@ #include "../../cpp/patch_core/archive_limits.h" #include "../../cpp/patch_core/archive_patch_core.h" #include "../../cpp/patch_core/digest.h" +#include "../../cpp/patch_core/native_texts.h" #include "../../cpp/patch_core/error_codes.h" #include "../../cpp/patch_core/install_record.h" #include "../../cpp/patch_core/patch_core.h" @@ -17,22 +18,22 @@ static NSString *const SOURCE_PATCH_NAME = @"__diff.json"; static NSString *const ERROR_FILE_OPERATION = @"file operation error"; -// Protocol strings stored encoded (RCTPushyRevealText) so they do not appear -// as plain binary strings; decoded on first use. +// Protocol strings stored encoded (cpp/patch_core/native_texts.h) so they do +// not appear as plain binary strings; decoded on first use. static NSString *BUNDLE_PATCH_NAME(void) { - static NSString *value = RCTPushyRevealText("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9"); + static NSString *value = PushyFromStdString(pushy::texts::BundleDeltaEntry()); return value; } PUSHY_HIDDEN NSString *PushyOptionUrlKey(void) { - static NSString *value = RCTPushyRevealText("2f07f0d0ba8e5d572e"); + static NSString *value = PushyFromStdString(pushy::texts::OptionUrlKey()); return value; } static NSString *PushyIpaDeltaSuffix(void) { - static NSString *value = RCTPushyRevealText("741ee4d0e09b69512137"); + static NSString *value = PushyFromStdString(pushy::texts::PackageDeltaSuffix()); return value; } static NSString *PushyPpkDeltaSuffix(void) { - static NSString *value = RCTPushyRevealText("7407e4dae09b69512137"); + static NSString *value = PushyFromStdString(pushy::texts::PpkDeltaSuffix()); return value; } diff --git a/ios/RCTPushy/RCTPushyOrchestrator.mm b/ios/RCTPushy/RCTPushyOrchestrator.mm index 800bb48d..68383fff 100644 --- a/ios/RCTPushy/RCTPushyOrchestrator.mm +++ b/ios/RCTPushy/RCTPushyOrchestrator.mm @@ -1,5 +1,6 @@ #import "RCTPushyInternal.h" #include "../../cpp/patch_core/error_codes.h" +#include "../../cpp/patch_core/native_texts.h" #include "../../cpp/update_flow_core/flow_json.h" #include "../../cpp/update_flow_core/native_config.h" #include "../../cpp/update_flow_core/round_engine.h" @@ -19,9 +20,10 @@ static NSString *const keyNativeCheckIncomplete = @"REACTNATIVECN_PUSHY_NATIVE_CHECK_INCOMPLETE_KEY"; // The crash-hold version-info flag read by JS, stored encoded -// (RCTPushyRevealText) so it does not appear as a plain binary string. +// (cpp/patch_core/native_texts.h) so it does not appear as a plain binary +// string. static NSString *PushyInfoCrashHold(void) { - static NSString *value = RCTPushyRevealText("3905f5c2a6b96d56212a19"); + static NSString *value = PushyFromStdString(pushy::texts::InfoCrashHold()); return value; } diff --git a/ios/RCTPushy/RCTPushyState.mm b/ios/RCTPushy/RCTPushyState.mm index b63fc98d..904928a6 100644 --- a/ios/RCTPushy/RCTPushyState.mm +++ b/ios/RCTPushy/RCTPushyState.mm @@ -5,6 +5,7 @@ #include #include "../../cpp/patch_core/digest.h" +#include "../../cpp/patch_core/native_texts.h" #import #import @@ -29,10 +30,10 @@ PUSHY_HIDDEN NSString *const keyHashInfo = @"REACTNATIVECN_PUSHY_HASH_"; PUSHY_HIDDEN NSString *const keyFirstLoadMarked = @"REACTNATIVECN_PUSHY_FIRSTLOADMARKED_KEY"; PUSHY_HIDDEN NSString *const keyRolledBackMarked = @"REACTNATIVECN_PUSHY_ROLLEDBACKMARKED_KEY"; -// Encoded constants (RCTPushyRevealText) are decoded on first use, not by +// Encoded constants (native_texts.h) are decoded on first use, not by // static initializers at image load. PUSHY_HIDDEN NSString *KeyPackageUpdatedMarked(void) { - static NSString *value = RCTPushyRevealText("0832d5f29aa549710b0939daf88ca058790f3dded7e888b451640d2ec3f69099bb43711523cafa8ea7bb4372132a"); + static NSString *value = PushyFromStdString(pushy::texts::PackageUpdatedMarkedKey()); return value; } // bundleHash cache: "|" where cacheKey identifies the diff --git a/scripts/contract.ts b/scripts/contract.ts index 0c3570b2..fafd0a2c 100644 --- a/scripts/contract.ts +++ b/scripts/contract.ts @@ -306,3 +306,109 @@ export const napiFunctions: Record = { runnableConfig: 'FlowRunnableConfig', normalizeConfiguration: 'FlowNormalizeConfiguration', }; + +export type TextHost = 'cpp' | 'java' | 'arkts'; + +export interface NativeText { + /** C++ function name; Java/ArkTS use its SCREAMING_SNAKE. */ + name: string; + value: string; + /** cpp covers the shared cores and iOS (cpp/patch_core/native_texts.h). */ + hosts: TextHost[]; + /** Per-host values where a platform names the same thing differently. */ + values?: Partial>; + doc?: string; +} + +// Plain text with update wording (or a service address) that shipped native +// code needs: stored encoded (scripts/encode-native-text.ts) so it never +// appears in the binaries' string tables, and decoded on first use. +export const nativeTexts: NativeText[] = [ + { + name: 'StatusNone', + value: 'noUpdate', + hosts: ['cpp', 'arkts'], + doc: 'The round status / decision reason when nothing is to be installed.', + }, + { + name: 'InfoCrashHold', + value: 'crashRescue', + hosts: ['cpp', 'java'], + doc: 'Version-info flag of a version a crash hold installed (read by JS).', + }, + { + name: 'InfoForceBoot', + value: 'forceBootRescue', + hosts: ['cpp'], + doc: 'Version-info flag of a version the server forceBoot-activated.', + }, + { name: 'QueryPath', value: '/checkUpdate/', hosts: ['cpp'] }, + { + name: 'PolicyNextLaunch', + value: 'setNeedUpdate', + hosts: ['cpp'], + doc: 'The afterDownload policy that activates for the next launch.', + }, + { name: 'FieldAvailable', value: 'update', hosts: ['cpp'] }, + { name: 'PreferencesName', value: 'update', hosts: ['java', 'arkts'] }, + { name: 'StorageDirName', value: '_update', hosts: ['java', 'arkts'] }, + { + name: 'BundleDeltaEntry', + value: 'index.bundlejs.patch', + hosts: ['cpp', 'java', 'arkts'], + values: { arkts: 'bundle.harmony.js.patch' }, + doc: 'The bundle patch entry inside a patch archive.', + }, + { name: 'OptionUrlKey', value: 'updateUrl', hosts: ['cpp', 'java'] }, + { name: 'PpkDeltaSuffix', value: '.ppk.patch', hosts: ['cpp', 'java', 'arkts'] }, + { + name: 'PackageDeltaSuffix', + value: '.ipa.patch', + hosts: ['cpp', 'java', 'arkts'], + values: { java: '.apk.patch', arkts: '.app.patch' }, + doc: 'Archive suffix of a patch against the installed package.', + }, + { + name: 'PackageUpdatedMarkedKey', + value: 'REACTNATIVECN_PUSHY_ISPACKAGEUPDATEDMARKED_KEY', + hosts: ['cpp'], + }, + { name: 'LogTag', value: 'react-native-update', hosts: ['cpp', 'java'] }, + { name: 'LibraryName', value: 'rnpushy', hosts: ['java'] }, + { + name: 'InstallerSessionExtra', + value: 'cn.reactnative.modules.update.extra.PACKAGE_INSTALLER_SESSION_ID', + hosts: ['java'], + }, + { name: 'ReloadMethod', value: 'reload', hosts: ['java'] }, + { name: 'DevtoolsRestartEvent', value: 'RELOAD', hosts: ['arkts'] }, + { name: 'DevtoolsRestartReason', value: 'HotReload2', hosts: ['arkts'] }, + { + name: 'StateResultClass', + value: 'cn/reactnative/modules/update/StateCoreResult', + hosts: ['cpp'], + }, + { + name: 'CopyGroupResultClass', + value: 'cn/reactnative/modules/update/CopyGroupResult', + hosts: ['cpp'], + }, +]; + +// Default service addresses: the JS client's presets, and Pushy's (the only +// brand native configure() defaults to) encoded for native code. +export const servicePresets = { + Pushy: { + main: ['https://update.react-native.cn/api', 'https://update.reactnative.cn/api'], + queryUrls: [ + 'https://gitee.com/sunnylqm/react-native-pushy/raw/master/endpoints.json', + 'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json', + ], + }, + Cresc: { + main: ['https://api.cresc.dev', 'https://api.cresc.app'], + queryUrls: [ + 'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints_cresc.json', + ], + }, +}; diff --git a/scripts/generate-contract.ts b/scripts/generate-contract.ts index 838e6558..4fbcd6c8 100644 --- a/scripts/generate-contract.ts +++ b/scripts/generate-contract.ts @@ -15,9 +15,12 @@ import { jniFunctions, maxQueryRequests, napiFunctions, + nativeTexts, roundStatuses, + servicePresets, stateOperations, syncStorageKeys, + type TextHost, vendoredSources, } from './contract'; import { encodeNativeText } from './encode-native-text'; @@ -597,6 +600,64 @@ region( ) ); +// Encoded text registry. +const textsFor = (host: TextHost) => + nativeTexts + .filter(({ hosts }) => hosts.includes(host)) + .map((text) => ({ ...text, value: text.values?.[host] ?? text.value })); +const cppStringList = (name: string, values: string[]) => [ + `inline std::vector ${name}() {`, + ' return {', + ...values.map((value) => ` text::Reveal("${encodeNativeText(value)}"),`), + ' };', + '}', +]; +region('cpp/patch_core/native_texts.h', 'texts', [ + ...textsFor('cpp').flatMap(({ name, value, doc }) => [ + ...comment(doc), + `inline const std::string& ${name}() {`, + ` static const std::string value = text::Reveal("${encodeNativeText(value)}");`, + ' return value;', + '}', + ]), + "// Pushy's own service addresses, configure()'s defaults.", + ...cppStringList('DefaultEndpoints', servicePresets.Pushy.main), + ...cppStringList('DefaultQueryUrls', servicePresets.Pushy.queryUrls), +]); +region( + `${javaDir}/Texts.java`, + 'texts', + textsFor('java').flatMap(({ name, value, doc }) => [ + ...comment(doc, 4), + `static final String ${snake(name)} = reveal("${encodeNativeText(value)}");`, + ]) +); +region( + `${ets}/Texts.ts`, + 'texts', + textsFor('arkts').flatMap(({ name, value, doc }) => [ + ...comment(doc), + `export const ${snake(name)}: string = revealText('${encodeNativeText(value)}');`, + ]) +); +// ` key: [...],` on one line when it fits (as biome formats it). +const jsArray = (key: string, values: string[]) => { + const line = ` ${key}: [${values.map((value) => `'${value}'`).join(', ')}],`; + return line.length <= 80 + ? [line] + : [` ${key}: [`, ...values.map((value) => ` '${value}',`), ' ],']; +}; +region('src/client.ts', 'service-presets', [ + 'const SERVER_PRESETS = {', + ...Object.entries(servicePresets).flatMap(([brand, { main, queryUrls }]) => [ + ` ${brand}: {`, + ...jsArray('main', main), + ...jsArray('queryUrls', queryUrls), + ' },', + ]), + '};', +]); + // --- Apply ----------------------------------------------------------------- const check = process.argv.includes('--check'); diff --git a/scripts/tests/ios-purge-restore/extract.py b/scripts/tests/ios-purge-restore/extract.py index fde7d4bb..a1230aef 100644 --- a/scripts/tests/ios-purge-restore/extract.py +++ b/scripts/tests/ios-purge-restore/extract.py @@ -94,7 +94,7 @@ def declaration(source: str, name: str) -> tuple[int, str]: HELPERS = [ # The exception boundary every entry point uses (CODE_AUDIT 4.4). 'PushyCaughtReason', 'PushyGuard', - 'KeyPackageUpdatedMarked', 'PushyWithStateLock', 'PushyToStdString', 'PushyFromStdString', + 'PushyWithStateLock', 'PushyToStdString', 'PushyFromStdString', 'KeyPackageUpdatedMarked', 'PushyJsonDictionary', 'PushyJsonString', 'PushyRejectCaught', 'PushyFileQueue', 'PushySetNullableString', 'PushyHashInfoKey', 'PushyBinaryIdentityValue', 'PushyStateFromDefaults', 'PushyApplyStateToDefaults', 'PushySwitchVersionLocked', diff --git a/scripts/tests/ios-purge-restore/purge_restore_test.mm b/scripts/tests/ios-purge-restore/purge_restore_test.mm index e86e7418..3436b9b3 100644 --- a/scripts/tests/ios-purge-restore/purge_restore_test.mm +++ b/scripts/tests/ios-purge-restore/purge_restore_test.mm @@ -18,6 +18,7 @@ #include "cpp/patch_core/error_codes.h" #include "cpp/patch_core/digest.h" #include "cpp/patch_core/install_record.h" +#include "cpp/patch_core/native_texts.h" #include "cpp/update_flow_core/native_config.h" #include "ios/RCTPushy/RCTPushyCommon.h" diff --git a/src/__tests__/nativeTextEncoding.test.ts b/src/__tests__/nativeTextEncoding.test.ts index 6fbed836..b35ea29d 100644 --- a/src/__tests__/nativeTextEncoding.test.ts +++ b/src/__tests__/nativeTextEncoding.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'bun:test'; import { revealText } from '../../harmony/pushy/src/main/ets/Texts'; +import { nativeTexts, servicePresets } from '../../scripts/contract'; import { decodeNativeText, encodeNativeText, @@ -7,13 +8,14 @@ import { import { shippedNativeSources, source, stripComments } from './nativeSources'; // Service addresses and the request path must only appear encoded in shipped -// native code (see scripts/encode-native-text.ts). +// native code (see scripts/encode-native-text.ts). They come from the same +// registry the generated code does (scripts/contract.ts). const plainTexts = [ - '/checkUpdate/', - 'https://update.react-native.cn/api', - 'https://update.reactnative.cn/api', - 'https://gitee.com/sunnylqm/react-native-pushy/raw/master/endpoints.json', - 'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json', + ...nativeTexts + .filter(({ name }) => name === 'QueryPath') + .map(({ value }) => value), + ...servicePresets.Pushy.main, + ...servicePresets.Pushy.queryUrls, ]; const paths = [ @@ -26,11 +28,20 @@ const paths = [ const encodedIn = (text: string) => [ ...text.matchAll( - /\b(?:reveal|RCTPushyRevealText|revealText|RevealStatic|Reveal)\(\s*["']([0-9a-f]+)["']\s*\)/g + /\b(?:reveal|revealText|RevealStatic|Reveal)\(\s*["']([0-9a-f]+)["']\s*\)/g ), ].map((match) => match[1]); +// The text of `code` outside its regions. +const handWritten = (code: string) => + code.replace(/[\s\S]*?<\/generated:\1>/g, ''); + describe('native text encoding', () => { + test('plain texts are known', () => { + expect(plainTexts).toContain('/checkUpdate/'); + expect(plainTexts.length).toBeGreaterThan(2); + }); + test('encoding round-trips', () => { for (const text of plainTexts) { expect(decodeNativeText(encodeNativeText(text))).toBe(text); @@ -38,9 +49,9 @@ describe('native text encoding', () => { } }); - // The request path is appended by the shared round core - // (cpp/update_flow_core/native_round.cpp); the default service addresses - // only live in the shared normalizer (cpp/update_flow_core/native_config.cpp). + // The request path and the default service addresses live in the generated + // cpp/patch_core/native_texts.h, used by the shared round core and the + // shared normalizer. test('the request path and the addresses are encoded in cpp/', () => { const shared = paths .filter((path) => path.startsWith('cpp/')) @@ -54,6 +65,19 @@ describe('native text encoding', () => { } }); + // Encoded text is registered once (scripts/contract.ts) and generated into + // native_texts.h / Texts.java / Texts.ts (error codes and the JNI table in + // their own generated regions); a hand-written encoded literal would be an + // unregistered copy that can drift. + test('every encoded literal is generated from the registry', () => { + const offenders = paths.flatMap((path) => + encodedIn(stripComments(handWritten(source(path)))).map( + (hex) => `${path}: ${decodeNativeText(hex)}` + ) + ); + expect(offenders).toEqual([]); + }); + test('shipped native code has no plain service addresses or paths', () => { const offenders = paths.flatMap((path) => { const code = stripComments(source(path)); diff --git a/src/client.ts b/src/client.ts index d219780d..bc41fecb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -14,8 +14,9 @@ import { assertWeb, info, log, noop, setDebugLogging } from './utils'; export type { UpdateErrorListener } from './clientReporter'; +// Pushy for China users, Cresc for international ones. +// (scripts/contract.ts; bun scripts/generate-contract.ts) const SERVER_PRESETS = { - // cn Pushy: { main: [ 'https://update.react-native.cn/api', @@ -26,7 +27,6 @@ const SERVER_PRESETS = { 'https://cdn.jsdelivr.net/gh/reactnativecn/react-native-update@master/endpoints.json', ], }, - // i18n Cresc: { main: ['https://api.cresc.dev', 'https://api.cresc.app'], queryUrls: [ @@ -34,6 +34,7 @@ const SERVER_PRESETS = { ], }, }; +// const cloneServerConfig = (server: UpdateServerConfig): UpdateServerConfig => ({ main: dedupeEndpoints([...(server.main || [])]),