Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ jobs:
run: bun install --frozen-lockfile

# `bun lint` ends with check-native-spec-parity.js (every
# src/NativePushy.ts method must be exported by all four native
# bindings, with the right sync/async kind on Harmony) and
# check-constant-parity.js (the C++ header constants must match their
# Java/ArkTS/JS mirrors).
# src/NativePushy.ts method must be implemented by all three native
# modules) and generate-contract.ts --check (every region generated from
# scripts/contract.ts and the calling-side declarations is current).
- name: Run lint
run: bun lint

Expand Down
2 changes: 2 additions & 0 deletions android/jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ Shared_Files := \
$(sort $(wildcard $(CPP_DIR)/hpatch/*.c))

# HDiffPatch / lzma submodules: only the decoder sources hpatch.c uses.
# <generated:vendored-sources> (scripts/contract.ts; bun scripts/generate-contract.ts)
Hdp_Files := \
HDiffPatch/libHDiffPatch/HPatch/patch.c \
HDiffPatch/file_for_patch.c \
lzma/C/LzmaDec.c \
lzma/C/Lzma2Dec.c
# </generated:vendored-sources>

LOCAL_SRC_FILES := \
$(patsubst $(LOCAL_PATH)/%,%,$(Shared_Files)) \
Expand Down
4 changes: 3 additions & 1 deletion android/jni/Application.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ APP_CPPFLAGS += -std=c++17 -Oz -fexceptions -fno-rtti -fno-unwind-tables -fno-as
APP_LDFLAGS += -Wl,--gc-sections -Wl,--exclude-libs,ALL
APP_LDFLAGS += -Wl,--icf=all
APP_BUILD_SCRIPT := Android.mk
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
# <generated:abis> (scripts/contract.ts; bun scripts/generate-contract.ts)
APP_ABI := arm64-v8a armeabi-v7a x86 x86_64
# </generated:abis>
APP_STL := c++_static
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
* itself (entry count, sizes, ratios, reserved names, the unknown-length
* probe plan) is the shared core's (cpp/patch_core/archive_limits.h, over
* JNI); the constants below are the ones this side still compares against
* itself, mirrored by hand (scripts/check-constant-parity.js asserts they
* match the header).
* itself, generated from scripts/contract.ts like the header.
*/
final class ArchiveLimits {
// <generated:limits> (scripts/contract.ts; bun scripts/generate-contract.ts)
static final long MAX_ARCHIVE_BYTES = 512L * 1024 * 1024;
static final long MAX_TOTAL_UNCOMPRESSED_BYTES = 2048L * 1024 * 1024;
static final long MAX_ENTRY_BYTES = 512L * 1024 * 1024;
static final long MAX_MANIFEST_BYTES = 16L * 1024 * 1024;
static final long FREE_DISK_MARGIN_BYTES = 64L * 1024 * 1024;
// </generated:limits>

private ArchiveLimits() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* mean that the running React Native instance has been reloaded.
*/
public final class BundlePreparationResult {
// <generated:statuses> (scripts/contract.ts; bun scripts/generate-contract.ts)
public static final String SKIPPED = "skipped";
public static final String NO_UPDATE = "noUpdate";
public static final String DOWNLOADED = "downloaded";
public static final String FAILED = "failed";
public static final String CANCELLED = "cancelled";
// </generated:statuses>

private final String status;
private final String reason;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
class DownloadTaskParams {
static final int TASK_TYPE_CLEANUP = 0; //Keep hash & originHash

static final int TASK_TYPE_PATCH_FULL = 1;
static final int TASK_TYPE_PATCH_FROM_APK = 2;
static final int TASK_TYPE_PATCH_FROM_PPK = 3;
// <generated:archive-patch-types> (scripts/contract.ts; bun scripts/generate-contract.ts)
static final int TASK_TYPE_PATCH_FULL = 1;
static final int TASK_TYPE_PATCH_FROM_APK = 2;
static final int TASK_TYPE_PATCH_FROM_PPK = 3;
// </generated:archive-patch-types>
static final int TASK_TYPE_PLAIN_DOWNLOAD = 4;

static final int DEFAULT_MAX_AGE_DAYS = 3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
* Stable, machine-readable error codes used as the promise rejection code so
* the JS layer and user loggers can aggregate errors across platforms.
*
* MUST stay in sync with cpp/patch_core/error_codes.h (the single source of
* truth) and src/error.ts (UpdateErrorCode). Messages are free-form; only the
* codes are part of the contract.
* The codes of cpp/patch_core/error_codes.h and src/error.ts
* (UpdateErrorCode), all generated from scripts/contract.ts. Messages are
* free-form; only the codes are part of the contract.
*/
final class ErrorCodes {
// <generated:error-codes> (scripts/contract.ts; bun scripts/generate-contract.ts)
static final String INVALID_OPTIONS = "INVALID_OPTIONS";
static final String DOWNLOAD_FAILED = "DOWNLOAD_FAILED";
// Encoded (see Texts.reveal): the value is "PATCH_FAILED".
Expand All @@ -22,6 +23,7 @@ final class ErrorCodes {
static final String UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM";
static final String APK_INSTALL_PERMISSION_REQUIRED = "APK_INSTALL_PERMISSION_REQUIRED";
static final String APK_INSTALL_FAILED = "APK_INSTALL_FAILED";
// </generated:error-codes>

private ErrorCodes() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
* isInstalled / recordedBundleSha256, which only stat and read the record.
*/
final class InstallRecord {
// Mirrors install_record.h kStagingSuffix (scripts/check-constant-parity.js).
// install_record.h kStagingSuffix, generated from scripts/contract.ts.
// <generated:staging-suffix> (scripts/contract.ts; bun scripts/generate-contract.ts)
static final String STAGING_SUFFIX = ".staging";
// </generated:staging-suffix>
/** The JS bundle inside a version directory. */
static final String BUNDLE_FILE_NAME = "index.bundlejs";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@ final class SyncCoordinator {
// Version-info flag read by JS (metadata.ts), stored encoded.
private static final String INFO_CRASH_HOLD = Texts.reveal("3905f5c2a6b96d56212a19");

// Shared with Harmony. The response cache lives in its own
// SharedPreferences file (UpdateContext.getNativeCheckCache).
// <generated:storage-keys> (scripts/contract.ts; bun scripts/generate-contract.ts)
// The persisted native check config (JS syncNativeConfig or the host).
static final String KEY_CONFIG = "nativeConfig";
// Raw response cache for the JS side to reuse (§10.3), scoped to the
// exact logical request and native config that produced it. Stored in
// its own SharedPreferences file (UpdateContext.getNativeCheckCache).
// Raw response cache for the JS side to reuse (§10.3), scoped to the exact
// logical request and native config that produced it.
static final String KEY_RESP_CACHE = "nativeCheckResp";
// Set when a round starts, cleared when it ends (§11.4). Residue on the
// next launch means the previous process died mid-round (a crash rescue
// was truncated): that launch resumes immediately instead of waiting 5s.
// next launch means the previous process died mid-round: that launch
// resumes immediately instead of waiting 5s.
static final String KEY_ROUND_INCOMPLETE = "nativeCheckIncomplete";
// </generated:storage-keys>
// The check response (and a remote queryUrls list) is a small JSON
// document; anything bigger is a broken or hijacked endpoint.
private static final long MAX_QUERY_RESPONSE_BYTES = 1024 * 1024;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ public class UpdateContext {
private String bundleHash;
private java.util.ArrayList<BundleHashListener> pendingBundleHashListeners =
new java.util.ArrayList<>();
// <generated:state-operations> (scripts/contract.ts; bun scripts/generate-contract.ts)
private static final int STATE_OP_SWITCH_VERSION = 1;
private static final int STATE_OP_MARK_SUCCESS = 2;
private static final int STATE_OP_ROLLBACK = 3;
// 4 (clear first time) exists in the state core but has no Android caller.
// 4 (ClearFirstTime) has no caller on this side.
private static final int STATE_OP_CLEAR_ROLLBACK_MARK = 5;
private static final int STATE_OP_RESOLVE_LAUNCH = 6;
// </generated:state-operations>
static final String KEY_FIRST_LOAD_MARKED = "firstLoadMarked";
// Bumped by resetToPackagedBundle. The cold-start check runs for minutes
// and may already hold a decision when the app resets to the packaged
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public final Map<String, Object> getConstants() {
return getTypedExportedConstants();
}

// <generated:spec-methods> (scripts/contract.ts; bun scripts/generate-contract.ts)
public abstract void setLocalHashInfo(String hash, String info, Promise promise);

public abstract void getLocalHashInfo(String hash, Promise promise);
Expand Down Expand Up @@ -66,4 +67,5 @@ public final Map<String, Object> getConstants() {
public abstract void addListener(String eventName);

public abstract void removeListeners(double count);
// </generated:spec-methods>
}
20 changes: 11 additions & 9 deletions cpp/android_jni/jni_natives.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,27 @@ namespace jni_natives {
// then throw a RuntimeException instead of defaulting fields.
bool InitResultClassCache(JNIEnv* env);

jint SupportedDiffVersion(JNIEnv*, jclass);
jobject SyncStateWithBinaryVersion(JNIEnv* env, jclass, jstring package_version, jstring build_time, jobject state_result);
jobject RunStateCore(JNIEnv* env, jclass, jint operation, jobject state_result, jstring string_arg, jboolean flag_a, jboolean flag_b);
// <generated:prototypes> (scripts/contract.ts; bun scripts/generate-contract.ts)
jint SupportedDiffVersion(JNIEnv* env, jclass);
jobject SyncStateWithBinaryVersion(JNIEnv* env, jclass, jstring package_version, jstring build_time, jobject state);
jobject RunStateCore(JNIEnv* env, jclass, jint operation, jobject state, jstring string_arg, jboolean flag_a, jboolean flag_b);
jobjectArray ApplyArchiveDelta(JNIEnv* env, jclass, jint patch_type, jobjectArray entry_names, jbyteArray manifest_json, jstring source_root, jstring target_root, jstring origin_bundle_path, jstring bundle_patch_path, jstring bundle_output_path, jboolean host_copies_resources);
void CleanupOldEntries(JNIEnv* env, jclass, jstring root_dir, jobjectArray keep_names, jint max_age_days, jstring trash_dir);
jstring FlowPrepareRound(JNIEnv* env, jclass, jstring configJson, jstring envJson, jdouble randomSample);
jstring FlowNextQueryStep(JNIEnv* env, jclass, jstring stateJson, jstring responseText);
jstring FlowStepRound(JNIEnv* env, jclass, jstring stateJson, jstring eventJson);
jstring FlowHandleResponse(JNIEnv* env, jclass, jstring responseText, jstring configJson, jstring envJson, jstring requestBody, jlong responseAtSeconds);
jstring FlowPrepareRound(JNIEnv* env, jclass, jstring config_json, jstring env_json, jdouble random_sample);
jstring FlowNextQueryStep(JNIEnv* env, jclass, jstring state_json, jstring response_text);
jstring FlowStepRound(JNIEnv* env, jclass, jstring state_json, jstring event_json);
jstring FlowHandleResponse(JNIEnv* env, jclass, jstring response_text, jstring config_json, jstring env_json, jstring request_body, jlong response_at_seconds);
jboolean FlowConfigsEquivalent(JNIEnv* env, jclass, jstring a, jstring b);
jstring FlowRunnableConfig(JNIEnv* env, jclass, jstring configJson);
jstring FlowNormalizeConfiguration(JNIEnv* env, jclass, jstring optionsJson);
jstring FlowRunnableConfig(JNIEnv* env, jclass, jstring config_json);
jstring FlowNormalizeConfiguration(JNIEnv* env, jclass, jstring options_json);
jboolean InstallRecordIsComplete(JNIEnv* env, jclass, jstring version_dir, jstring version_hash, jstring bundle_file_name);
jstring InstallRecordCheckActivation(JNIEnv* env, jclass, jstring version_dir, jstring version_hash, jstring bundle_file_name, jboolean grandfathered);
jstring InstallRecordSeal(JNIEnv* env, jclass, jstring staging_dir, jstring version_hash, jstring bundle_file_name, jstring artifact_sha256, jstring known_bundle_sha256);
jstring InstallRecordPromote(JNIEnv* env, jclass, jstring staging_dir, jstring version_dir);
jstring InstallRecordBundleSha256(JNIEnv* env, jclass, jstring version_dir);
jlong ArchiveCheckEntries(JNIEnv* env, jclass, jlong archive_bytes, jobjectArray names, jlongArray sizes, jlongArray compressed_sizes);
jlongArray ArchivePlanFreeSpaceProbe(JNIEnv* env, jclass, jlong known_total, jlong written_before, jlong chunk_bytes, jlong reserved_until);
// </generated:prototypes>

} // namespace jni_natives
} // namespace pushy
Expand Down
2 changes: 2 additions & 0 deletions cpp/android_jni/jni_registration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct NativeClass {

const std::vector<NativeClass>& NativeClasses() {
static const std::vector<NativeClass> classes = {
// <generated:natives> (scripts/contract.ts; bun scripts/generate-contract.ts)
{"3919bbc3ab8a6b512c3e08f0c0b6df60452311edfbc8f780624b2d1de38c8ebc8e7e42342de4daa0", // NativeCore
{
{"3d12e0e2bb9b784a302b19fdf2ba966b7c2216f2f7d4b6", "725edd",
Expand Down Expand Up @@ -84,6 +85,7 @@ const std::vector<NativeClass>& NativeClasses() {
{"2a1bf5df88996d40112f1dfad38382624822", "723ddefb84c2536f",
reinterpret_cast<void*>(&pushy::jni_natives::ArchivePlanFreeSpaceProbe)},
}},
// </generated:natives>
};
return classes;
}
Expand Down
8 changes: 5 additions & 3 deletions cpp/patch_core/archive_limits.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
// and CheckExtractedDirectory after, over NAPI (pushy.cpp).
// The constants a host still compares against itself (download caps, the
// manifest cap, the free-space margin, the streamed-bytes backstop) are
// mirrored in android/.../ArchiveLimits.java and
// harmony/pushy/src/main/ets/ArchiveLimits.ts;
// scripts/check-constant-parity.js (part of `bun lint`) asserts they match.
// generated into android/.../ArchiveLimits.java and
// harmony/pushy/src/main/ets/ArchiveLimits.ts from scripts/contract.ts, like
// the values below.

// Keep the core internal to whichever binary links it (see podspec Core).
#pragma GCC visibility push(hidden)
Expand All @@ -35,6 +35,7 @@ namespace archive_limits {

using delta::Status;

// <generated:limits> (scripts/contract.ts; bun scripts/generate-contract.ts)
// Downloaded archive (Content-Length up front, streamed bytes as backstop).
constexpr long long kMaxArchiveBytes = 512LL * 1024 * 1024;
// Sum of every entry's uncompressed size.
Expand All @@ -58,6 +59,7 @@ constexpr long long kFreeDiskMarginBytes = 64LL * 1024 * 1024;
// probe reserving at least this many bytes ahead (a single larger chunk
// reserves its own size), so no write can ever eat into the margin.
constexpr long long kUnknownLengthFreeSpaceProbeBytes = 8LL * 1024 * 1024;
// </generated:limits>

// Whether any component of an archive path ('/' or '\\' separated) starts
// with install_record::kReservedPrefix. Those names belong to the SDK (the
Expand Down
2 changes: 2 additions & 0 deletions cpp/patch_core/archive_patch_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ namespace pushy {
namespace archive_patch {

enum class ArchivePatchType {
// <generated:archive-patch-types> (scripts/contract.ts; bun scripts/generate-contract.ts)
kFull = 1,
kPatchFromPackage = 2,
kPatchFromPpk = 3,
// </generated:archive-patch-types>
};

enum class EntryAction {
Expand Down
22 changes: 11 additions & 11 deletions cpp/patch_core/error_codes.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
#ifndef PUSHY_PATCH_CORE_ERROR_CODES_H_
#define PUSHY_PATCH_CORE_ERROR_CODES_H_

// Single source of truth for the stable, machine-readable error codes shared
// by every platform. Native modules reject promises with one of these codes
// so the JS layer (src/error.ts UpdateErrorCode) and user loggers can
// aggregate errors across platforms and locales.
// The stable, machine-readable error codes shared by every platform. Native
// modules reject promises with one of these codes so the JS layer
// (src/error.ts UpdateErrorCode) and user loggers can aggregate errors across
// platforms and locales.
//
// Mirrors that cannot include this header MUST stay in sync by hand
// (scripts/check-constant-parity.js, part of `bun lint`, asserts they do):
// - src/error.ts (UpdateErrorCode, JS layer)
// - android/.../ErrorCodes.java (Java constants)
// - harmony/pushy/src/main/ets/ErrorCodes.ts
// iOS (ios/RCTPushy/*.mm) includes this header directly.
// Generated, like its copies in src/error.ts, android/.../ErrorCodes.java and
// harmony/pushy/src/main/ets/ErrorCodes.ts, from scripts/contract.ts. iOS
// (ios/RCTPushy/*.mm) includes this header directly.
//
// Human-readable messages are NOT part of this contract: they may differ per
// platform and locale. Only the codes are stable.
Expand All @@ -24,12 +21,14 @@
namespace pushy {
namespace error_codes {

// <generated:error-codes> (scripts/contract.ts; bun scripts/generate-contract.ts)
// Method options missing or malformed (blank hash/url, wrong types).
constexpr const char* kInvalidOptions = "INVALID_OPTIONS";
// Native download failed (network error, bad HTTP status, truncated body).
constexpr const char* kDownloadFailed = "DOWNLOAD_FAILED";
// Unzip or hdiff patch application failed.
inline const char* const kDeltaFailed = text::RevealStatic("0a36c0f286b44e640b1339dd");
inline const char* const kDeltaFailed =
text::RevealStatic("0a36c0f286b44e640b1339dd");
// Local file or state persistence operation failed.
constexpr const char* kFileOperationFailed = "FILE_OPERATION_FAILED";
// switchVersion / setNeedUpdate state transition failed.
Expand All @@ -52,6 +51,7 @@ constexpr const char* kApkInstallPermissionRequired =
"APK_INSTALL_PERMISSION_REQUIRED";
// PackageInstaller could not create, write, commit, or continue an APK session.
constexpr const char* kApkInstallFailed = "APK_INSTALL_FAILED";
// </generated:error-codes>

} // namespace error_codes
} // namespace pushy
Expand Down
8 changes: 5 additions & 3 deletions cpp/patch_core/install_record.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
// This is the only implementation: Android (InstallRecord.java over JNI,
// cpp/android_jni/install_record_jni.cpp), iOS (RCTPushyInstallRecord.mm)
// and Harmony (InstallRecord.ts over NAPI, pushy.cpp) call it. The bundle
// file name is the caller's, since it differs per platform. Constants a
// host still mirrors are listed in scripts/check-constant-parity.js
// (part of `bun lint`), which asserts they match.
// file name is the caller's, since it differs per platform. The constants
// (and the staging suffix the hosts build their unpack directory from) are
// generated from scripts/contract.ts.

// Keep the core internal to whichever binary links it (see podspec Core).
#pragma GCC visibility push(hidden)
Expand All @@ -43,6 +43,7 @@ namespace install_record {

using delta::Status;

// <generated:constants> (scripts/contract.ts; bun scripts/generate-contract.ts)
constexpr int kSchema = 1;
constexpr const char* kFileName = ".pushy-complete";
// Every name starting with this prefix, at any depth of a version directory,
Expand All @@ -52,6 +53,7 @@ constexpr const char* kReservedPrefix = ".pushy-";
constexpr const char* kStagingSuffix = ".staging";
// A record is a few hundred bytes; anything larger is not one.
constexpr long long kMaxRecordBytes = 64 * 1024;
// </generated:constants>

// A record file's contents.
struct Record {
Expand Down
2 changes: 2 additions & 0 deletions cpp/patch_core/state_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ namespace pushy {
namespace state_ops {

enum class StateOperation {
// <generated:state-operations> (scripts/contract.ts; bun scripts/generate-contract.ts)
kSwitchVersion = 1,
kMarkSuccess = 2,
kRollback = 3,
kClearFirstTime = 4,
kClearRollbackMark = 5,
kResolveLaunch = 6,
// </generated:state-operations>
};

} // namespace state_ops
Expand Down
6 changes: 4 additions & 2 deletions cpp/update_flow_core/native_round.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ namespace flowcore {

// Total HTTP requests one query may issue: endpoint queries and remote list
// fetches alike. The JS check's endpoint fallback keeps the same budget for
// its endpoint queries (src/endpoint.ts MAX_ENDPOINT_REQUESTS,
// scripts/check-constant-parity.js).
// its endpoint queries (src/endpoint.ts MAX_ENDPOINT_REQUESTS; both generated
// from scripts/contract.ts).
// <generated:query-budget> (scripts/contract.ts; bun scripts/generate-contract.ts)
constexpr int kMaxQueryRequests = 8;
// </generated:query-budget>

// {"body": "<checkUpdate request text>", "query": <first NextQueryStep
// state>}. The body is BuildRequestBody over the config and env: a non-empty
Expand Down
2 changes: 2 additions & 0 deletions harmony/pushy/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ file(GLOB CORE_SOURCES CONFIGURE_DEPENDS
set(HDP_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/pushy.cpp
${CORE_SOURCES}
# <generated:vendored-sources> (scripts/contract.ts; bun scripts/generate-contract.ts)
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch/patch.c
${HDIFFPATCH_DIR}/file_for_patch.c
${LZMA_DIR}/C/LzmaDec.c
${LZMA_DIR}/C/Lzma2Dec.c
# </generated:vendored-sources>
)

add_library(rnpushy SHARED
Expand Down
Loading
Loading