react-native-update 10.59.2 → 10.60.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +2 -7
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +17 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2634
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +73 -82
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
#include <napi/native_api.h>
|
|
2
2
|
|
|
3
3
|
#include "flow_json.h"
|
|
4
|
-
#include "
|
|
4
|
+
#include "native_config.h"
|
|
5
|
+
#include "native_round.h"
|
|
5
6
|
#include <js_native_api.h>
|
|
6
7
|
#include <js_native_api_types.h>
|
|
7
8
|
|
|
9
|
+
#include <cerrno>
|
|
10
|
+
#include <cstdint>
|
|
8
11
|
#include <string>
|
|
12
|
+
#include <unistd.h>
|
|
13
|
+
#include <utility>
|
|
9
14
|
#include <vector>
|
|
10
15
|
|
|
16
|
+
#include "archive_limits.h"
|
|
11
17
|
#include "archive_patch_core.h"
|
|
12
18
|
#include "digest.h"
|
|
19
|
+
#include "install_record.h"
|
|
13
20
|
#include "hbc_transform_wire.h"
|
|
14
21
|
#include "patch_core.h"
|
|
15
22
|
#include "state_core.h"
|
|
16
23
|
#include "state_ops.h"
|
|
17
24
|
|
|
18
|
-
extern "C" {
|
|
19
|
-
}
|
|
20
|
-
|
|
21
25
|
namespace {
|
|
22
26
|
|
|
23
27
|
using pushy::state_ops::StateOperation;
|
|
@@ -314,7 +318,11 @@ napi_value NewBoolean(napi_env env, bool value) {
|
|
|
314
318
|
|
|
315
319
|
napi_value NewString(napi_env env, const std::string& value) {
|
|
316
320
|
napi_value result = nullptr;
|
|
317
|
-
napi_create_string_utf8(env, value.c_str(), value.size(), &result)
|
|
321
|
+
if (napi_create_string_utf8(env, value.c_str(), value.size(), &result) !=
|
|
322
|
+
napi_ok) {
|
|
323
|
+
ThrowError(env, "Failed to create string");
|
|
324
|
+
return nullptr;
|
|
325
|
+
}
|
|
318
326
|
return result;
|
|
319
327
|
}
|
|
320
328
|
|
|
@@ -337,14 +345,19 @@ void SetBoolProperty(
|
|
|
337
345
|
napi_set_named_property(env, object, name, NewBoolean(env, value));
|
|
338
346
|
}
|
|
339
347
|
|
|
348
|
+
// Per-operation outputs reported next to the state; all default to "nothing".
|
|
349
|
+
struct StateResultExtras {
|
|
350
|
+
bool changed = false;
|
|
351
|
+
std::string stale_version_to_delete;
|
|
352
|
+
std::string load_version;
|
|
353
|
+
bool did_rollback = false;
|
|
354
|
+
bool consumed_first_time = false;
|
|
355
|
+
};
|
|
356
|
+
|
|
340
357
|
napi_value NewStateResult(
|
|
341
358
|
napi_env env,
|
|
342
359
|
const pushy::state::State& state,
|
|
343
|
-
|
|
344
|
-
const std::string& stale_version_to_delete,
|
|
345
|
-
const std::string& load_version,
|
|
346
|
-
bool did_rollback,
|
|
347
|
-
bool consumed_first_time) {
|
|
360
|
+
const StateResultExtras& extras = StateResultExtras()) {
|
|
348
361
|
napi_value result = nullptr;
|
|
349
362
|
napi_create_object(env, &result);
|
|
350
363
|
|
|
@@ -355,21 +368,12 @@ napi_value NewStateResult(
|
|
|
355
368
|
SetBoolProperty(env, result, "firstTime", state.first_time);
|
|
356
369
|
SetBoolProperty(env, result, "firstTimeOk", state.first_time_ok);
|
|
357
370
|
SetStringProperty(env, result, "rolledBackVersion", state.rolled_back_version);
|
|
358
|
-
SetBoolProperty(env, result, "changed", changed);
|
|
359
|
-
SetStringProperty(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
SetBoolProperty(env, result, "
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
napi_value NewArchivePatchPlanResult(
|
|
367
|
-
napi_env env,
|
|
368
|
-
const pushy::archive_patch::ArchivePatchPlan& plan) {
|
|
369
|
-
napi_value result = nullptr;
|
|
370
|
-
napi_create_object(env, &result);
|
|
371
|
-
SetStringProperty(env, result, "mergeSourceSubdir", plan.merge_source_subdir);
|
|
372
|
-
SetBoolProperty(env, result, "enableMerge", plan.enable_merge);
|
|
371
|
+
SetBoolProperty(env, result, "changed", extras.changed);
|
|
372
|
+
SetStringProperty(
|
|
373
|
+
env, result, "staleVersionToDelete", extras.stale_version_to_delete);
|
|
374
|
+
SetStringProperty(env, result, "loadVersion", extras.load_version);
|
|
375
|
+
SetBoolProperty(env, result, "didRollback", extras.did_rollback);
|
|
376
|
+
SetBoolProperty(env, result, "consumedFirstTime", extras.consumed_first_time);
|
|
373
377
|
return result;
|
|
374
378
|
}
|
|
375
379
|
|
|
@@ -394,24 +398,18 @@ napi_value NewCopyGroupArray(
|
|
|
394
398
|
NewString(env, groups[index].to_paths[target_index]));
|
|
395
399
|
}
|
|
396
400
|
napi_set_named_property(env, group, "toPaths", to_paths);
|
|
401
|
+
if (groups[index].has_expected_crc) {
|
|
402
|
+
// A CRC32 fits a double exactly, like the manifest's JSON number.
|
|
403
|
+
napi_value crc = nullptr;
|
|
404
|
+
napi_create_double(
|
|
405
|
+
env, static_cast<double>(groups[index].expected_crc), &crc);
|
|
406
|
+
napi_set_named_property(env, group, "expectedCrc", crc);
|
|
407
|
+
}
|
|
397
408
|
napi_set_element(env, result, index, group);
|
|
398
409
|
}
|
|
399
410
|
return result;
|
|
400
411
|
}
|
|
401
412
|
|
|
402
|
-
pushy::delta::PatchManifest BuildManifest(
|
|
403
|
-
const std::vector<std::string>& copy_froms,
|
|
404
|
-
const std::vector<std::string>& copy_tos,
|
|
405
|
-
const std::vector<std::string>& deletes) {
|
|
406
|
-
pushy::delta::PatchManifest manifest;
|
|
407
|
-
for (size_t index = 0; index < copy_froms.size(); ++index) {
|
|
408
|
-
manifest.copies.push_back(
|
|
409
|
-
pushy::delta::CopyOperation{copy_froms[index], copy_tos[index]});
|
|
410
|
-
}
|
|
411
|
-
manifest.deletes = deletes;
|
|
412
|
-
return manifest;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
413
|
napi_value SyncStateWithBinaryVersion(napi_env env, napi_callback_info info) {
|
|
416
414
|
napi_value args[3] = {nullptr, nullptr, nullptr};
|
|
417
415
|
size_t argc = 3;
|
|
@@ -436,7 +434,9 @@ napi_value SyncStateWithBinaryVersion(napi_env env, napi_callback_info info) {
|
|
|
436
434
|
|
|
437
435
|
const pushy::state::BinaryVersionSyncResult result =
|
|
438
436
|
pushy::state::SyncBinaryVersion(state, package_version, build_time);
|
|
439
|
-
|
|
437
|
+
StateResultExtras extras;
|
|
438
|
+
extras.changed = result.changed;
|
|
439
|
+
return NewStateResult(env, result.state, extras);
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
napi_value RunStateCore(napi_env env, napi_callback_info info) {
|
|
@@ -477,59 +477,32 @@ napi_value RunStateCore(napi_env env, napi_callback_info info) {
|
|
|
477
477
|
|
|
478
478
|
switch (static_cast<StateOperation>(operation)) {
|
|
479
479
|
case StateOperation::kSwitchVersion:
|
|
480
|
-
return NewStateResult(
|
|
481
|
-
env,
|
|
482
|
-
pushy::state::SwitchVersion(state, string_arg),
|
|
483
|
-
false,
|
|
484
|
-
std::string(),
|
|
485
|
-
std::string(),
|
|
486
|
-
false,
|
|
487
|
-
false);
|
|
480
|
+
return NewStateResult(env, pushy::state::SwitchVersion(state, string_arg));
|
|
488
481
|
case StateOperation::kMarkSuccess: {
|
|
489
482
|
const pushy::state::MarkSuccessResult result = pushy::state::MarkSuccess(state);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
false,
|
|
494
|
-
result.stale_version_to_delete,
|
|
495
|
-
std::string(),
|
|
496
|
-
false,
|
|
497
|
-
false);
|
|
483
|
+
StateResultExtras extras;
|
|
484
|
+
extras.stale_version_to_delete = result.stale_version_to_delete;
|
|
485
|
+
return NewStateResult(env, result.state, extras);
|
|
498
486
|
}
|
|
499
487
|
case StateOperation::kRollback: {
|
|
500
488
|
const pushy::state::State next = pushy::state::Rollback(state);
|
|
501
|
-
|
|
502
|
-
|
|
489
|
+
StateResultExtras extras;
|
|
490
|
+
extras.load_version = next.current_version;
|
|
491
|
+
extras.did_rollback = true;
|
|
492
|
+
return NewStateResult(env, next, extras);
|
|
503
493
|
}
|
|
504
494
|
case StateOperation::kClearFirstTime:
|
|
505
|
-
return NewStateResult(
|
|
506
|
-
env,
|
|
507
|
-
pushy::state::ClearFirstTime(state),
|
|
508
|
-
false,
|
|
509
|
-
std::string(),
|
|
510
|
-
std::string(),
|
|
511
|
-
false,
|
|
512
|
-
false);
|
|
495
|
+
return NewStateResult(env, pushy::state::ClearFirstTime(state));
|
|
513
496
|
case StateOperation::kClearRollbackMark:
|
|
514
|
-
return NewStateResult(
|
|
515
|
-
env,
|
|
516
|
-
pushy::state::ClearRollbackMark(state),
|
|
517
|
-
false,
|
|
518
|
-
std::string(),
|
|
519
|
-
std::string(),
|
|
520
|
-
false,
|
|
521
|
-
false);
|
|
497
|
+
return NewStateResult(env, pushy::state::ClearRollbackMark(state));
|
|
522
498
|
case StateOperation::kResolveLaunch: {
|
|
523
499
|
const pushy::state::LaunchDecision decision =
|
|
524
500
|
pushy::state::ResolveLaunchState(state, flag_a, flag_b);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
decision.load_version,
|
|
531
|
-
decision.did_rollback,
|
|
532
|
-
decision.consumed_first_time);
|
|
501
|
+
StateResultExtras extras;
|
|
502
|
+
extras.load_version = decision.load_version;
|
|
503
|
+
extras.did_rollback = decision.did_rollback;
|
|
504
|
+
extras.consumed_first_time = decision.consumed_first_time;
|
|
505
|
+
return NewStateResult(env, decision.state, extras);
|
|
533
506
|
}
|
|
534
507
|
}
|
|
535
508
|
|
|
@@ -537,105 +510,10 @@ napi_value RunStateCore(napi_env env, napi_callback_info info) {
|
|
|
537
510
|
return nullptr;
|
|
538
511
|
}
|
|
539
512
|
|
|
540
|
-
napi_value BuildArchivePatchPlan(napi_env env, napi_callback_info info) {
|
|
541
|
-
napi_value args[6] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
|
|
542
|
-
size_t argc = 6;
|
|
543
|
-
if (!GetArgCount(env, info, &argc, args) || argc < 5) {
|
|
544
|
-
ThrowError(env, "Wrong number of arguments");
|
|
545
|
-
return nullptr;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
int32_t patch_type = 0;
|
|
549
|
-
if (!GetInt32(env, args[0], &patch_type)) {
|
|
550
|
-
return nullptr;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
std::vector<std::string> entry_names;
|
|
554
|
-
std::vector<std::string> copy_froms;
|
|
555
|
-
std::vector<std::string> copy_tos;
|
|
556
|
-
std::vector<std::string> deletes;
|
|
557
|
-
if (!GetStringArray(env, args[1], &entry_names) ||
|
|
558
|
-
!GetStringArray(env, args[2], ©_froms) ||
|
|
559
|
-
!GetStringArray(env, args[3], ©_tos) ||
|
|
560
|
-
!GetStringArray(env, args[4], &deletes)) {
|
|
561
|
-
return nullptr;
|
|
562
|
-
}
|
|
563
|
-
if (copy_froms.size() != copy_tos.size()) {
|
|
564
|
-
ThrowError(env, "copyFroms and copyTos length mismatch");
|
|
565
|
-
return nullptr;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
std::string bundle_patch_entry_name =
|
|
569
|
-
pushy::archive_patch::DefaultBundleDeltaEntryName();
|
|
570
|
-
if (argc >= 6) {
|
|
571
|
-
bool ok = false;
|
|
572
|
-
const std::string candidate = GetString(env, args[5], &ok);
|
|
573
|
-
if (!ok) {
|
|
574
|
-
return nullptr;
|
|
575
|
-
}
|
|
576
|
-
if (!candidate.empty()) {
|
|
577
|
-
bundle_patch_entry_name = candidate;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const pushy::delta::PatchManifest manifest =
|
|
582
|
-
BuildManifest(copy_froms, copy_tos, deletes);
|
|
583
|
-
pushy::archive_patch::ArchivePatchType archive_type;
|
|
584
|
-
if (!pushy::archive_patch::TryParseArchivePatchType(patch_type, &archive_type)) {
|
|
585
|
-
ThrowError(env, "Unknown archive delta type");
|
|
586
|
-
return nullptr;
|
|
587
|
-
}
|
|
588
|
-
pushy::archive_patch::ArchivePatchPlan plan;
|
|
589
|
-
const pushy::delta::Status status = pushy::archive_patch::BuildArchivePatchPlan(
|
|
590
|
-
archive_type,
|
|
591
|
-
manifest,
|
|
592
|
-
entry_names,
|
|
593
|
-
&plan,
|
|
594
|
-
bundle_patch_entry_name);
|
|
595
|
-
if (!status.ok) {
|
|
596
|
-
ThrowError(env, status.message);
|
|
597
|
-
return nullptr;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
return NewArchivePatchPlanResult(env, plan);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
napi_value BuildCopyGroups(napi_env env, napi_callback_info info) {
|
|
604
|
-
napi_value args[2] = {nullptr, nullptr};
|
|
605
|
-
size_t argc = 2;
|
|
606
|
-
if (!GetArgCount(env, info, &argc, args) || argc < 2) {
|
|
607
|
-
ThrowError(env, "Wrong number of arguments");
|
|
608
|
-
return nullptr;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
std::vector<std::string> copy_froms;
|
|
612
|
-
std::vector<std::string> copy_tos;
|
|
613
|
-
if (!GetStringArray(env, args[0], ©_froms) ||
|
|
614
|
-
!GetStringArray(env, args[1], ©_tos)) {
|
|
615
|
-
return nullptr;
|
|
616
|
-
}
|
|
617
|
-
if (copy_froms.size() != copy_tos.size()) {
|
|
618
|
-
ThrowError(env, "copyFroms and copyTos length mismatch");
|
|
619
|
-
return nullptr;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
const pushy::delta::PatchManifest manifest =
|
|
623
|
-
BuildManifest(copy_froms, copy_tos, std::vector<std::string>());
|
|
624
|
-
std::vector<pushy::archive_patch::CopyGroup> groups;
|
|
625
|
-
const pushy::delta::Status status =
|
|
626
|
-
pushy::archive_patch::BuildCopyGroups(manifest, &groups);
|
|
627
|
-
if (!status.ok) {
|
|
628
|
-
ThrowError(env, status.message);
|
|
629
|
-
return nullptr;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
return NewCopyGroupArray(env, groups);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
513
|
// ---------------------------------------------------------------------------
|
|
636
514
|
// Async work plumbing for the heavy patch operations.
|
|
637
515
|
//
|
|
638
|
-
//
|
|
516
|
+
// applyArchiveDelta and cleanupOldEntries run hdiff / recursive file IO
|
|
639
517
|
// that can take hundreds of ms to seconds. The Pushy TurboModule executes on
|
|
640
518
|
// the UI thread, so running these synchronously froze the UI. These are now
|
|
641
519
|
// wrapped in napi_create_async_work: arguments are parsed on the JS thread, the
|
|
@@ -643,8 +521,7 @@ napi_value BuildCopyGroups(napi_env env, napi_callback_info info) {
|
|
|
643
521
|
// back on the JS thread.
|
|
644
522
|
// ---------------------------------------------------------------------------
|
|
645
523
|
|
|
646
|
-
// Reject an already-created deferred with an Error(message).
|
|
647
|
-
// work fails to be created/queued, so the Promise never hangs pending.
|
|
524
|
+
// Reject an already-created deferred with an Error(message).
|
|
648
525
|
void RejectDeferredWithMessage(
|
|
649
526
|
napi_env env,
|
|
650
527
|
napi_deferred deferred,
|
|
@@ -656,16 +533,102 @@ void RejectDeferredWithMessage(
|
|
|
656
533
|
napi_reject_deferred(env, deferred, error);
|
|
657
534
|
}
|
|
658
535
|
|
|
659
|
-
|
|
536
|
+
void ResolveDeferredWithUndefined(napi_env env, napi_deferred deferred) {
|
|
537
|
+
napi_value undefined_value = nullptr;
|
|
538
|
+
napi_get_undefined(env, &undefined_value);
|
|
539
|
+
napi_resolve_deferred(env, deferred, undefined_value);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Resolves with undefined, or rejects with the status message.
|
|
543
|
+
void SettleWithStatus(
|
|
544
|
+
napi_env env,
|
|
545
|
+
napi_deferred deferred,
|
|
546
|
+
const pushy::delta::Status& status) {
|
|
547
|
+
if (status.ok) {
|
|
548
|
+
ResolveDeferredWithUndefined(env, deferred);
|
|
549
|
+
} else {
|
|
550
|
+
RejectDeferredWithMessage(env, deferred, status.message.c_str());
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
template <typename W>
|
|
555
|
+
struct AsyncCall {
|
|
660
556
|
napi_async_work work = nullptr;
|
|
661
557
|
napi_deferred deferred = nullptr;
|
|
662
|
-
|
|
558
|
+
W data;
|
|
559
|
+
void (*execute)(W&) = nullptr;
|
|
560
|
+
void (*settle)(napi_env, napi_deferred, const W&) = nullptr;
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// Runs execute(data) on a libuv worker thread and returns a Promise that
|
|
564
|
+
// settle(env, deferred, data) settles back on the JS thread. Every path
|
|
565
|
+
// settles the promise exactly once and frees the call: a creation or queue
|
|
566
|
+
// failure rejects immediately, and work that was cancelled or aborted before
|
|
567
|
+
// it ran is rejected instead of settled from meaningless results.
|
|
568
|
+
template <typename W>
|
|
569
|
+
napi_value QueueWork(
|
|
570
|
+
napi_env env,
|
|
571
|
+
const char* name,
|
|
572
|
+
W data,
|
|
573
|
+
void (*execute)(W&),
|
|
574
|
+
void (*settle)(napi_env, napi_deferred, const W&)) {
|
|
575
|
+
auto* call = new AsyncCall<W>();
|
|
576
|
+
call->data = std::move(data);
|
|
577
|
+
call->execute = execute;
|
|
578
|
+
call->settle = settle;
|
|
579
|
+
|
|
580
|
+
napi_value promise = nullptr;
|
|
581
|
+
if (napi_create_promise(env, &call->deferred, &promise) != napi_ok) {
|
|
582
|
+
delete call;
|
|
583
|
+
ThrowError(env, "Unable to create promise");
|
|
584
|
+
return nullptr;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
napi_value resource_name = nullptr;
|
|
588
|
+
napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &resource_name);
|
|
589
|
+
if (napi_create_async_work(
|
|
590
|
+
env,
|
|
591
|
+
nullptr,
|
|
592
|
+
resource_name,
|
|
593
|
+
[](napi_env, void* raw) {
|
|
594
|
+
auto* c = static_cast<AsyncCall<W>*>(raw);
|
|
595
|
+
c->execute(c->data);
|
|
596
|
+
},
|
|
597
|
+
[](napi_env cb_env, napi_status status, void* raw) {
|
|
598
|
+
auto* c = static_cast<AsyncCall<W>*>(raw);
|
|
599
|
+
if (status != napi_ok) {
|
|
600
|
+
RejectDeferredWithMessage(cb_env, c->deferred, "async work aborted");
|
|
601
|
+
} else {
|
|
602
|
+
c->settle(cb_env, c->deferred, c->data);
|
|
603
|
+
}
|
|
604
|
+
napi_delete_async_work(cb_env, c->work);
|
|
605
|
+
delete c;
|
|
606
|
+
},
|
|
607
|
+
call,
|
|
608
|
+
&call->work) != napi_ok) {
|
|
609
|
+
// Work was never created: settle the promise and free the call so it
|
|
610
|
+
// neither leaks nor hangs pending forever.
|
|
611
|
+
RejectDeferredWithMessage(env, call->deferred, "Unable to create async work");
|
|
612
|
+
delete call;
|
|
613
|
+
return promise;
|
|
614
|
+
}
|
|
615
|
+
if (napi_queue_async_work(env, call->work) != napi_ok) {
|
|
616
|
+
// Queueing failed: the complete callback will never run, so clean up here.
|
|
617
|
+
napi_delete_async_work(env, call->work);
|
|
618
|
+
RejectDeferredWithMessage(env, call->deferred, "Unable to queue async work");
|
|
619
|
+
delete call;
|
|
620
|
+
return promise;
|
|
621
|
+
}
|
|
622
|
+
return promise;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
struct ApplyArchiveDeltaWork {
|
|
626
|
+
pushy::archive_patch::ArchivePatchRequest request;
|
|
627
|
+
std::vector<pushy::archive_patch::CopyGroup> host_copies;
|
|
663
628
|
pushy::delta::Status status{false, ""};
|
|
664
629
|
};
|
|
665
630
|
|
|
666
631
|
struct CleanupWork {
|
|
667
|
-
napi_async_work work = nullptr;
|
|
668
|
-
napi_deferred deferred = nullptr;
|
|
669
632
|
std::string root_dir;
|
|
670
633
|
// Every version that may still be in use: persisted current/last AND the
|
|
671
634
|
// version this process booted from (two switches without a restart must not
|
|
@@ -679,13 +642,76 @@ struct CleanupWork {
|
|
|
679
642
|
// of MB; hashing it synchronously on the UI thread (where the TurboModule
|
|
680
643
|
// runs) froze the app for hundreds of ms per archive/bundle/activation.
|
|
681
644
|
struct Sha256FileWork {
|
|
682
|
-
napi_async_work work = nullptr;
|
|
683
|
-
napi_deferred deferred = nullptr;
|
|
684
645
|
std::string path;
|
|
685
646
|
std::string hex;
|
|
686
647
|
};
|
|
687
648
|
|
|
688
|
-
|
|
649
|
+
// Digest of a byte range of an fd the caller keeps open (rawfile
|
|
650
|
+
// descriptors: the embedded bundle lives at [offset, offset + length) inside
|
|
651
|
+
// the HAP). The fd is never closed here; JS closes it after the promise
|
|
652
|
+
// settles.
|
|
653
|
+
struct Sha256RangeWork {
|
|
654
|
+
int fd = -1;
|
|
655
|
+
int64_t offset = 0;
|
|
656
|
+
int64_t length = 0;
|
|
657
|
+
std::string hex;
|
|
658
|
+
std::string error;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
// pread loop (positioned: never moves a file offset the caller may share).
|
|
662
|
+
// A short read before `length` bytes is an error, not EOF.
|
|
663
|
+
void HashFdRange(Sha256RangeWork& w) {
|
|
664
|
+
pushy::digest::Sha256 hasher;
|
|
665
|
+
std::vector<uint8_t> buffer(256 * 1024);
|
|
666
|
+
int64_t position = w.offset;
|
|
667
|
+
int64_t remaining = w.length;
|
|
668
|
+
while (remaining > 0) {
|
|
669
|
+
const size_t want = static_cast<size_t>(
|
|
670
|
+
remaining < static_cast<int64_t>(buffer.size())
|
|
671
|
+
? remaining
|
|
672
|
+
: static_cast<int64_t>(buffer.size()));
|
|
673
|
+
const ssize_t read = pread(w.fd, buffer.data(), want, static_cast<off_t>(position));
|
|
674
|
+
if (read < 0) {
|
|
675
|
+
if (errno == EINTR) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
w.error = "sha256HexRawFdAsync: read failed (errno " +
|
|
679
|
+
std::to_string(errno) + ")";
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (read == 0) {
|
|
683
|
+
w.error = "sha256HexRawFdAsync: unexpected end of file";
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
hasher.Update(buffer.data(), static_cast<size_t>(read));
|
|
687
|
+
position += read;
|
|
688
|
+
remaining -= read;
|
|
689
|
+
}
|
|
690
|
+
w.hex = hasher.HexDigest();
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
bool GetNonNegativeInt64(
|
|
694
|
+
napi_env env,
|
|
695
|
+
napi_value value,
|
|
696
|
+
const char* name,
|
|
697
|
+
int64_t* out_value) {
|
|
698
|
+
napi_valuetype type = napi_undefined;
|
|
699
|
+
if (value == nullptr || !GetValueType(env, value, &type) ||
|
|
700
|
+
type != napi_number ||
|
|
701
|
+
napi_get_value_int64(env, value, out_value) != napi_ok ||
|
|
702
|
+
*out_value < 0) {
|
|
703
|
+
ThrowError(env, std::string("sha256HexRawFdAsync: ") + name +
|
|
704
|
+
" must be a non-negative number");
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// applyArchiveDelta(request) -> Promise<CopyGroupResult[]>. The manifest
|
|
711
|
+
// text is parsed (with the rest of the patch) on the worker thread; the
|
|
712
|
+
// promise resolves with the copies the caller still owes (empty unless
|
|
713
|
+
// hostCopiesResources) and rejects with the core's message.
|
|
714
|
+
napi_value ApplyArchiveDelta(napi_env env, napi_callback_info info) {
|
|
689
715
|
napi_value args[1] = {nullptr};
|
|
690
716
|
size_t argc = 1;
|
|
691
717
|
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
@@ -696,105 +722,60 @@ napi_value ApplyPatchFromFileSource(napi_env env, napi_callback_info info) {
|
|
|
696
722
|
return nullptr;
|
|
697
723
|
}
|
|
698
724
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
std::string
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
!GetOptionalStringArrayProperty(env, args[0], "copyTos", ©_tos) ||
|
|
713
|
-
!GetOptionalStringArrayProperty(env, args[0], "deletes", &deletes) ||
|
|
714
|
-
!GetOptionalStringProperty(env, args[0], "sourceRoot", &source_root) ||
|
|
715
|
-
!GetOptionalStringProperty(env, args[0], "targetRoot", &target_root) ||
|
|
716
|
-
!GetOptionalStringProperty(env, args[0], "originBundlePath", &origin_bundle_path) ||
|
|
717
|
-
!GetOptionalStringProperty(env, args[0], "bundleDeltaPath", &bundle_patch_path) ||
|
|
718
|
-
!GetOptionalStringProperty(env, args[0], "bundleOutputPath", &bundle_output_path) ||
|
|
719
|
-
!GetOptionalStringProperty(env, args[0], "mergeSourceSubdir", &merge_source_subdir) ||
|
|
725
|
+
ApplyArchiveDeltaWork work;
|
|
726
|
+
pushy::archive_patch::ArchivePatchRequest& request = work.request;
|
|
727
|
+
napi_value patch_type_value = nullptr;
|
|
728
|
+
int32_t patch_type = 0;
|
|
729
|
+
std::string bundle_patch_entry_name;
|
|
730
|
+
if (!GetNamedProperty(env, args[0], "archiveType", &patch_type_value) ||
|
|
731
|
+
!GetInt32(env, patch_type_value, &patch_type) ||
|
|
732
|
+
!GetOptionalStringArrayProperty(
|
|
733
|
+
env, args[0], "entryNames", &request.archive.entry_names) ||
|
|
734
|
+
!GetOptionalStringProperty(
|
|
735
|
+
env, args[0], "manifestJson", &request.archive.manifest_json) ||
|
|
736
|
+
!GetOptionalStringProperty(
|
|
737
|
+
env, args[0], "bundleDeltaEntry", &bundle_patch_entry_name) ||
|
|
720
738
|
!GetOptionalStringProperty(
|
|
721
|
-
env, args[0], "
|
|
722
|
-
!
|
|
739
|
+
env, args[0], "copySourcePrefix", &request.archive.copy_source_prefix) ||
|
|
740
|
+
!GetOptionalStringProperty(env, args[0], "sourceRoot", &request.source_root) ||
|
|
741
|
+
!GetOptionalStringProperty(env, args[0], "targetRoot", &request.target_root) ||
|
|
742
|
+
!GetOptionalStringProperty(
|
|
743
|
+
env, args[0], "originBundlePath", &request.origin_bundle_path) ||
|
|
744
|
+
!GetOptionalStringProperty(
|
|
745
|
+
env, args[0], "bundleDeltaPath", &request.bundle_patch_path) ||
|
|
746
|
+
!GetOptionalStringProperty(
|
|
747
|
+
env, args[0], "bundleOutputPath", &request.bundle_output_path) ||
|
|
748
|
+
!GetOptionalBoolProperty(
|
|
749
|
+
env, args[0], "hostCopiesResources", false,
|
|
750
|
+
&request.host_copies_resources)) {
|
|
723
751
|
return nullptr;
|
|
724
752
|
}
|
|
725
|
-
|
|
726
|
-
|
|
753
|
+
// The shared parser keeps Android and HarmonyOS in agreement on which
|
|
754
|
+
// integer values are valid; neither silently falls back to kFull.
|
|
755
|
+
if (!pushy::archive_patch::TryParseArchivePatchType(
|
|
756
|
+
patch_type, &request.archive.type)) {
|
|
757
|
+
ThrowError(env, "Unknown archive delta type");
|
|
727
758
|
return nullptr;
|
|
728
759
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
work_data->options.manifest = BuildManifest(copy_froms, copy_tos, deletes);
|
|
732
|
-
work_data->options.source_root = source_root;
|
|
733
|
-
work_data->options.target_root = target_root;
|
|
734
|
-
work_data->options.origin_bundle_path = origin_bundle_path;
|
|
735
|
-
work_data->options.bundle_patch_path = bundle_patch_path;
|
|
736
|
-
work_data->options.bundle_output_path = bundle_output_path;
|
|
737
|
-
work_data->options.merge_source_subdir = merge_source_subdir;
|
|
738
|
-
work_data->options.bundle_hbc_transform_meta = bundle_hbc_transform_meta;
|
|
739
|
-
work_data->options.enable_merge = enable_merge;
|
|
740
|
-
|
|
741
|
-
napi_value promise = nullptr;
|
|
742
|
-
if (napi_create_promise(env, &work_data->deferred, &promise) != napi_ok) {
|
|
743
|
-
delete work_data;
|
|
744
|
-
ThrowError(env, "Unable to create promise");
|
|
745
|
-
return nullptr;
|
|
760
|
+
if (!bundle_patch_entry_name.empty()) {
|
|
761
|
+
request.archive.bundle_patch_entry_name = bundle_patch_entry_name;
|
|
746
762
|
}
|
|
747
763
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
// meaningless; still settle the promise so it never hangs.
|
|
764
|
-
RejectDeferredWithMessage(cb_env, w->deferred, "async work aborted");
|
|
765
|
-
} else if (w->status.ok) {
|
|
766
|
-
napi_value undefined_value = nullptr;
|
|
767
|
-
napi_get_undefined(cb_env, &undefined_value);
|
|
768
|
-
napi_resolve_deferred(cb_env, w->deferred, undefined_value);
|
|
769
|
-
} else {
|
|
770
|
-
napi_value error = nullptr;
|
|
771
|
-
napi_value message = nullptr;
|
|
772
|
-
napi_create_string_utf8(
|
|
773
|
-
cb_env, w->status.message.c_str(), NAPI_AUTO_LENGTH, &message);
|
|
774
|
-
napi_create_error(cb_env, nullptr, message, &error);
|
|
775
|
-
napi_reject_deferred(cb_env, w->deferred, error);
|
|
776
|
-
}
|
|
777
|
-
napi_delete_async_work(cb_env, w->work);
|
|
778
|
-
delete w;
|
|
779
|
-
},
|
|
780
|
-
work_data,
|
|
781
|
-
&work_data->work) != napi_ok) {
|
|
782
|
-
// Work was never created: settle the promise and free the data so it does
|
|
783
|
-
// not leak / hang pending forever.
|
|
784
|
-
RejectDeferredWithMessage(
|
|
785
|
-
env, work_data->deferred, "Unable to create async work");
|
|
786
|
-
delete work_data;
|
|
787
|
-
return promise;
|
|
788
|
-
}
|
|
789
|
-
if (napi_queue_async_work(env, work_data->work) != napi_ok) {
|
|
790
|
-
// Queued failed: the complete callback will never run, so clean up here.
|
|
791
|
-
napi_delete_async_work(env, work_data->work);
|
|
792
|
-
RejectDeferredWithMessage(
|
|
793
|
-
env, work_data->deferred, "Unable to queue async work");
|
|
794
|
-
delete work_data;
|
|
795
|
-
return promise;
|
|
796
|
-
}
|
|
797
|
-
return promise;
|
|
764
|
+
return QueueWork<ApplyArchiveDeltaWork>(
|
|
765
|
+
env,
|
|
766
|
+
"applyArchiveDelta",
|
|
767
|
+
std::move(work),
|
|
768
|
+
[](ApplyArchiveDeltaWork& w) {
|
|
769
|
+
w.status =
|
|
770
|
+
pushy::archive_patch::ApplyArchivePatch(w.request, &w.host_copies);
|
|
771
|
+
},
|
|
772
|
+
[](napi_env cb_env, napi_deferred deferred, const ApplyArchiveDeltaWork& w) {
|
|
773
|
+
if (!w.status.ok) {
|
|
774
|
+
RejectDeferredWithMessage(cb_env, deferred, w.status.message.c_str());
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
napi_resolve_deferred(cb_env, deferred, NewCopyGroupArray(cb_env, w.host_copies));
|
|
778
|
+
});
|
|
798
779
|
}
|
|
799
780
|
|
|
800
781
|
// cleanupOldEntries(rootDir, keepNames: string[], maxAgeDays)
|
|
@@ -820,64 +801,22 @@ napi_value CleanupOldEntries(napi_env env, napi_callback_info info) {
|
|
|
820
801
|
return nullptr;
|
|
821
802
|
}
|
|
822
803
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
napi_value promise = nullptr;
|
|
829
|
-
if (napi_create_promise(env, &work_data->deferred, &promise) != napi_ok) {
|
|
830
|
-
delete work_data;
|
|
831
|
-
ThrowError(env, "Unable to create promise");
|
|
832
|
-
return nullptr;
|
|
833
|
-
}
|
|
804
|
+
CleanupWork work;
|
|
805
|
+
work.root_dir = root_dir;
|
|
806
|
+
work.keep_names = std::move(keep_names);
|
|
807
|
+
work.max_age_days = max_age_days;
|
|
834
808
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
},
|
|
847
|
-
[](napi_env cb_env, napi_status status, void* data) {
|
|
848
|
-
auto* w = static_cast<CleanupWork*>(data);
|
|
849
|
-
if (status != napi_ok) {
|
|
850
|
-
RejectDeferredWithMessage(cb_env, w->deferred, "async work aborted");
|
|
851
|
-
} else if (w->status.ok) {
|
|
852
|
-
napi_value undefined_value = nullptr;
|
|
853
|
-
napi_get_undefined(cb_env, &undefined_value);
|
|
854
|
-
napi_resolve_deferred(cb_env, w->deferred, undefined_value);
|
|
855
|
-
} else {
|
|
856
|
-
napi_value error = nullptr;
|
|
857
|
-
napi_value message = nullptr;
|
|
858
|
-
napi_create_string_utf8(
|
|
859
|
-
cb_env, w->status.message.c_str(), NAPI_AUTO_LENGTH, &message);
|
|
860
|
-
napi_create_error(cb_env, nullptr, message, &error);
|
|
861
|
-
napi_reject_deferred(cb_env, w->deferred, error);
|
|
862
|
-
}
|
|
863
|
-
napi_delete_async_work(cb_env, w->work);
|
|
864
|
-
delete w;
|
|
865
|
-
},
|
|
866
|
-
work_data,
|
|
867
|
-
&work_data->work) != napi_ok) {
|
|
868
|
-
RejectDeferredWithMessage(
|
|
869
|
-
env, work_data->deferred, "Unable to create async work");
|
|
870
|
-
delete work_data;
|
|
871
|
-
return promise;
|
|
872
|
-
}
|
|
873
|
-
if (napi_queue_async_work(env, work_data->work) != napi_ok) {
|
|
874
|
-
napi_delete_async_work(env, work_data->work);
|
|
875
|
-
RejectDeferredWithMessage(
|
|
876
|
-
env, work_data->deferred, "Unable to queue async work");
|
|
877
|
-
delete work_data;
|
|
878
|
-
return promise;
|
|
879
|
-
}
|
|
880
|
-
return promise;
|
|
809
|
+
return QueueWork<CleanupWork>(
|
|
810
|
+
env,
|
|
811
|
+
"cleanupOldEntries",
|
|
812
|
+
std::move(work),
|
|
813
|
+
[](CleanupWork& w) {
|
|
814
|
+
w.status = pushy::delta::CleanupOldEntries(
|
|
815
|
+
w.root_dir, w.keep_names, w.max_age_days);
|
|
816
|
+
},
|
|
817
|
+
[](napi_env cb_env, napi_deferred deferred, const CleanupWork& w) {
|
|
818
|
+
SettleWithStatus(cb_env, deferred, w.status);
|
|
819
|
+
});
|
|
881
820
|
}
|
|
882
821
|
|
|
883
822
|
// sha256HexFileAsync(path) -> Promise<hex>. Rejects when the file cannot be
|
|
@@ -899,144 +838,334 @@ napi_value Sha256HexFileAsync(napi_env env, napi_callback_info info) {
|
|
|
899
838
|
return nullptr;
|
|
900
839
|
}
|
|
901
840
|
|
|
902
|
-
|
|
903
|
-
|
|
841
|
+
Sha256FileWork work;
|
|
842
|
+
work.path = std::move(path);
|
|
904
843
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
844
|
+
return QueueWork<Sha256FileWork>(
|
|
845
|
+
env,
|
|
846
|
+
"sha256HexFileAsync",
|
|
847
|
+
std::move(work),
|
|
848
|
+
[](Sha256FileWork& w) { w.hex = pushy::digest::Sha256File(w.path); },
|
|
849
|
+
[](napi_env cb_env, napi_deferred deferred, const Sha256FileWork& w) {
|
|
850
|
+
if (w.hex.empty()) {
|
|
851
|
+
RejectDeferredWithMessage(
|
|
852
|
+
cb_env, deferred, "sha256HexFileAsync: cannot read file");
|
|
853
|
+
} else {
|
|
854
|
+
napi_resolve_deferred(cb_env, deferred, NewString(cb_env, w.hex));
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// sha256HexRawFdAsync(fd, offset, length) -> Promise<hex>, hashed on a libuv
|
|
860
|
+
// worker thread. Used for the embedded bundle (resourceManager.getRawFd): the
|
|
861
|
+
// UI thread neither reads the multi-MB bundle into memory nor hashes it.
|
|
862
|
+
// Rejects on any read failure or short read.
|
|
863
|
+
napi_value Sha256HexRawFdAsync(napi_env env, napi_callback_info info) {
|
|
864
|
+
napi_value args[3] = {nullptr, nullptr, nullptr};
|
|
865
|
+
size_t argc = 3;
|
|
866
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 3) {
|
|
867
|
+
ThrowError(env, "sha256HexRawFdAsync: expected (fd, offset, length)");
|
|
909
868
|
return nullptr;
|
|
910
869
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
env, "sha256HexFileAsync", NAPI_AUTO_LENGTH, &resource_name);
|
|
915
|
-
if (napi_create_async_work(
|
|
916
|
-
env,
|
|
917
|
-
nullptr,
|
|
918
|
-
resource_name,
|
|
919
|
-
[](napi_env, void* data) {
|
|
920
|
-
auto* w = static_cast<Sha256FileWork*>(data);
|
|
921
|
-
w->hex = pushy::digest::Sha256File(w->path);
|
|
922
|
-
},
|
|
923
|
-
[](napi_env cb_env, napi_status status, void* data) {
|
|
924
|
-
auto* w = static_cast<Sha256FileWork*>(data);
|
|
925
|
-
if (status != napi_ok) {
|
|
926
|
-
RejectDeferredWithMessage(cb_env, w->deferred, "async work aborted");
|
|
927
|
-
} else if (w->hex.empty()) {
|
|
928
|
-
RejectDeferredWithMessage(
|
|
929
|
-
cb_env, w->deferred, "sha256HexFileAsync: cannot read file");
|
|
930
|
-
} else {
|
|
931
|
-
napi_resolve_deferred(cb_env, w->deferred, NewString(cb_env, w->hex));
|
|
932
|
-
}
|
|
933
|
-
napi_delete_async_work(cb_env, w->work);
|
|
934
|
-
delete w;
|
|
935
|
-
},
|
|
936
|
-
work_data,
|
|
937
|
-
&work_data->work) != napi_ok) {
|
|
938
|
-
RejectDeferredWithMessage(
|
|
939
|
-
env, work_data->deferred, "Unable to create async work");
|
|
940
|
-
delete work_data;
|
|
941
|
-
return promise;
|
|
870
|
+
int32_t fd = -1;
|
|
871
|
+
if (!GetInt32(env, args[0], &fd)) {
|
|
872
|
+
return nullptr;
|
|
942
873
|
}
|
|
943
|
-
if (
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
env, work_data->deferred, "Unable to queue async work");
|
|
947
|
-
delete work_data;
|
|
948
|
-
return promise;
|
|
874
|
+
if (fd < 0) {
|
|
875
|
+
ThrowError(env, "sha256HexRawFdAsync: fd must be non-negative");
|
|
876
|
+
return nullptr;
|
|
949
877
|
}
|
|
950
|
-
|
|
878
|
+
int64_t offset = 0;
|
|
879
|
+
int64_t length = 0;
|
|
880
|
+
if (!GetNonNegativeInt64(env, args[1], "offset", &offset) ||
|
|
881
|
+
!GetNonNegativeInt64(env, args[2], "length", &length)) {
|
|
882
|
+
return nullptr;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
Sha256RangeWork work;
|
|
886
|
+
work.fd = fd;
|
|
887
|
+
work.offset = offset;
|
|
888
|
+
work.length = length;
|
|
889
|
+
|
|
890
|
+
return QueueWork<Sha256RangeWork>(
|
|
891
|
+
env,
|
|
892
|
+
"sha256HexRawFdAsync",
|
|
893
|
+
std::move(work),
|
|
894
|
+
HashFdRange,
|
|
895
|
+
[](napi_env cb_env, napi_deferred deferred, const Sha256RangeWork& w) {
|
|
896
|
+
if (!w.error.empty() || w.hex.empty()) {
|
|
897
|
+
RejectDeferredWithMessage(
|
|
898
|
+
cb_env,
|
|
899
|
+
deferred,
|
|
900
|
+
w.error.empty() ? "sha256HexRawFdAsync: no digest"
|
|
901
|
+
: w.error.c_str());
|
|
902
|
+
} else {
|
|
903
|
+
napi_resolve_deferred(cb_env, deferred, NewString(cb_env, w.hex));
|
|
904
|
+
}
|
|
905
|
+
});
|
|
951
906
|
}
|
|
952
907
|
|
|
953
|
-
|
|
908
|
+
// ---- Completion records (cpp/patch_core/install_record.h) ----
|
|
909
|
+
|
|
910
|
+
// Reads the first `count` arguments as strings, of which the first
|
|
911
|
+
// `non_empty` must not be empty; throws and returns false otherwise.
|
|
912
|
+
bool GetStringArgs(
|
|
954
913
|
napi_env env,
|
|
955
|
-
|
|
914
|
+
napi_callback_info info,
|
|
956
915
|
const char* name,
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
916
|
+
size_t count,
|
|
917
|
+
size_t non_empty,
|
|
918
|
+
std::string* out) {
|
|
919
|
+
napi_value args[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
|
|
920
|
+
size_t argc = count;
|
|
921
|
+
if (count > 5 || !GetArgCount(env, info, &argc, args) || argc < count) {
|
|
922
|
+
ThrowError(env, std::string(name) + ": wrong number of arguments");
|
|
962
923
|
return false;
|
|
963
924
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
925
|
+
for (size_t i = 0; i < count; i++) {
|
|
926
|
+
bool ok = false;
|
|
927
|
+
out[i] = GetString(env, args[i], &ok);
|
|
928
|
+
if (!ok) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
if (i < non_empty && out[i].empty()) {
|
|
932
|
+
ThrowError(env, std::string(name) + ": argument " + std::to_string(i) +
|
|
933
|
+
" must be a non-empty string");
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
968
936
|
}
|
|
969
937
|
return true;
|
|
970
938
|
}
|
|
971
939
|
|
|
972
|
-
|
|
940
|
+
// isInstallComplete(versionDir, versionHash, bundleFileName) -> boolean.
|
|
941
|
+
// Synchronous: a stat and a read of the (at most 64 KB) record, no digest.
|
|
942
|
+
napi_value InstallRecordIsComplete(napi_env env, napi_callback_info info) {
|
|
943
|
+
std::string args[3];
|
|
944
|
+
if (!GetStringArgs(env, info, "isInstallComplete", 3, 3, args)) {
|
|
945
|
+
return nullptr;
|
|
946
|
+
}
|
|
947
|
+
return NewBoolean(env, pushy::install_record::IsComplete(args[0], args[1], args[2]));
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// recordedBundleSha256(versionDir) -> string; "" when the record has none.
|
|
951
|
+
// Synchronous like isInstallComplete.
|
|
952
|
+
napi_value InstallRecordBundleSha256(napi_env env, napi_callback_info info) {
|
|
953
|
+
std::string args[1];
|
|
954
|
+
if (!GetStringArgs(env, info, "recordedBundleSha256", 1, 1, args)) {
|
|
955
|
+
return nullptr;
|
|
956
|
+
}
|
|
957
|
+
return NewString(env, pushy::install_record::RecordedBundleSha256(args[0]));
|
|
958
|
+
}
|
|
973
959
|
|
|
960
|
+
// One install_record call on a libuv worker thread: hashing a bundle (tens
|
|
961
|
+
// of MB) or fsyncing must not block the JS thread.
|
|
962
|
+
struct InstallRecordWork {
|
|
963
|
+
std::string args[4];
|
|
964
|
+
bool grandfathered = false;
|
|
965
|
+
pushy::delta::Status status{false, ""};
|
|
966
|
+
};
|
|
974
967
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
968
|
+
void SettleInstallRecordWork(
|
|
969
|
+
napi_env env,
|
|
970
|
+
napi_deferred deferred,
|
|
971
|
+
const InstallRecordWork& w) {
|
|
972
|
+
SettleWithStatus(env, deferred, w.status);
|
|
973
|
+
}
|
|
978
974
|
|
|
979
|
-
//
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
985
|
-
ThrowError(env, "sha256HexFile: missing path argument");
|
|
975
|
+
// sealInstallRecord(stagingDir, versionHash, bundleFileName, artifactSha256)
|
|
976
|
+
// -> Promise<void>: hashes the staged bundle and writes the record.
|
|
977
|
+
napi_value SealInstallRecord(napi_env env, napi_callback_info info) {
|
|
978
|
+
InstallRecordWork work;
|
|
979
|
+
if (!GetStringArgs(env, info, "sealInstallRecord", 4, 3, work.args)) {
|
|
986
980
|
return nullptr;
|
|
987
981
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
982
|
+
return QueueWork<InstallRecordWork>(
|
|
983
|
+
env,
|
|
984
|
+
"sealInstallRecord",
|
|
985
|
+
std::move(work),
|
|
986
|
+
[](InstallRecordWork& w) {
|
|
987
|
+
w.status = pushy::install_record::Seal(
|
|
988
|
+
w.args[0], w.args[1], w.args[2], w.args[3], std::string())
|
|
989
|
+
.status;
|
|
990
|
+
},
|
|
991
|
+
SettleInstallRecordWork);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// promoteStaging(stagingDir, versionDir) -> Promise<void>: renames the sealed
|
|
995
|
+
// staging directory to the (removed) version directory.
|
|
996
|
+
napi_value PromoteStaging(napi_env env, napi_callback_info info) {
|
|
997
|
+
InstallRecordWork work;
|
|
998
|
+
if (!GetStringArgs(env, info, "promoteStaging", 2, 2, work.args)) {
|
|
992
999
|
return nullptr;
|
|
993
1000
|
}
|
|
994
|
-
return
|
|
1001
|
+
return QueueWork<InstallRecordWork>(
|
|
1002
|
+
env,
|
|
1003
|
+
"promoteStaging",
|
|
1004
|
+
std::move(work),
|
|
1005
|
+
[](InstallRecordWork& w) {
|
|
1006
|
+
w.status = pushy::install_record::Promote(w.args[0], w.args[1]);
|
|
1007
|
+
},
|
|
1008
|
+
SettleInstallRecordWork);
|
|
995
1009
|
}
|
|
996
1010
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1011
|
+
// checkActivation(versionDir, versionHash, bundleFileName, grandfathered)
|
|
1012
|
+
// -> Promise<void>, rejected with the reason the version must not be
|
|
1013
|
+
// activated.
|
|
1014
|
+
napi_value CheckActivation(napi_env env, napi_callback_info info) {
|
|
1015
|
+
InstallRecordWork work;
|
|
1016
|
+
if (!GetStringArgs(env, info, "checkActivation", 3, 3, work.args)) {
|
|
1002
1017
|
return nullptr;
|
|
1003
1018
|
}
|
|
1019
|
+
napi_value args[4] = {nullptr, nullptr, nullptr, nullptr};
|
|
1020
|
+
size_t argc = 4;
|
|
1021
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 4) {
|
|
1022
|
+
ThrowError(env, "checkActivation: wrong number of arguments");
|
|
1023
|
+
return nullptr;
|
|
1024
|
+
}
|
|
1025
|
+
if (!GetBoolean(env, args[3], &work.grandfathered)) {
|
|
1026
|
+
return nullptr;
|
|
1027
|
+
}
|
|
1028
|
+
return QueueWork<InstallRecordWork>(
|
|
1029
|
+
env,
|
|
1030
|
+
"checkActivation",
|
|
1031
|
+
std::move(work),
|
|
1032
|
+
[](InstallRecordWork& w) {
|
|
1033
|
+
pushy::install_record::Activation activation;
|
|
1034
|
+
w.status = pushy::install_record::CheckActivation(
|
|
1035
|
+
w.args[0], w.args[1], w.args[2], w.grandfathered, &activation);
|
|
1036
|
+
},
|
|
1037
|
+
SettleInstallRecordWork);
|
|
1038
|
+
}
|
|
1004
1039
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
size_t
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (!
|
|
1023
|
-
|
|
1024
|
-
ThrowError(env, "
|
|
1025
|
-
|
|
1040
|
+
// ---- Archive extraction limits (cpp/patch_core/archive_limits.h) ----
|
|
1041
|
+
|
|
1042
|
+
// Reads `count` number arguments (byte counts: integers below 2^53).
|
|
1043
|
+
bool GetInt64Args(
|
|
1044
|
+
napi_env env,
|
|
1045
|
+
napi_callback_info info,
|
|
1046
|
+
const char* name,
|
|
1047
|
+
size_t count,
|
|
1048
|
+
int64_t* out) {
|
|
1049
|
+
napi_value args[4] = {nullptr, nullptr, nullptr, nullptr};
|
|
1050
|
+
size_t argc = count;
|
|
1051
|
+
if (count > 4 || !GetArgCount(env, info, &argc, args) || argc < count) {
|
|
1052
|
+
ThrowError(env, std::string(name) + ": wrong number of arguments");
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
for (size_t i = 0; i < count; i++) {
|
|
1056
|
+
napi_valuetype type = napi_undefined;
|
|
1057
|
+
if (!GetValueType(env, args[i], &type) || type != napi_number ||
|
|
1058
|
+
napi_get_value_int64(env, args[i], &out[i]) != napi_ok) {
|
|
1059
|
+
ThrowError(env, std::string(name) + ": argument " + std::to_string(i) +
|
|
1060
|
+
" must be a number");
|
|
1061
|
+
return false;
|
|
1026
1062
|
}
|
|
1027
1063
|
}
|
|
1064
|
+
return true;
|
|
1065
|
+
}
|
|
1028
1066
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1067
|
+
// checkUncompressedSize(archiveBytes, uncompressedBytes) -> string | undefined:
|
|
1068
|
+
// the reason the whole archive's expansion is refused.
|
|
1069
|
+
napi_value CheckUncompressedSize(napi_env env, napi_callback_info info) {
|
|
1070
|
+
int64_t args[2] = {0, 0};
|
|
1071
|
+
if (!GetInt64Args(env, info, "checkUncompressedSize", 2, args)) {
|
|
1072
|
+
return nullptr;
|
|
1032
1073
|
}
|
|
1033
|
-
const
|
|
1074
|
+
const pushy::delta::Status status =
|
|
1075
|
+
pushy::archive_limits::CheckUncompressedSize(args[0], args[1]);
|
|
1076
|
+
return status.ok ? nullptr : NewString(env, status.message);
|
|
1077
|
+
}
|
|
1034
1078
|
|
|
1079
|
+
// planFreeSpaceProbe(knownTotal, writtenBefore, chunkBytes, reservedUntil)
|
|
1080
|
+
// -> {probe, reserve, reservedUntil}, before one chunk of a download is
|
|
1081
|
+
// written.
|
|
1082
|
+
napi_value PlanFreeSpaceProbe(napi_env env, napi_callback_info info) {
|
|
1083
|
+
int64_t args[4] = {0, 0, 0, 0};
|
|
1084
|
+
if (!GetInt64Args(env, info, "planFreeSpaceProbe", 4, args)) {
|
|
1085
|
+
return nullptr;
|
|
1086
|
+
}
|
|
1087
|
+
const pushy::archive_limits::FreeSpaceProbe plan =
|
|
1088
|
+
pushy::archive_limits::PlanUnknownLengthFreeSpaceProbe(
|
|
1089
|
+
args[0], args[1], args[2], args[3]);
|
|
1035
1090
|
napi_value result = nullptr;
|
|
1036
|
-
|
|
1091
|
+
napi_value reserve = nullptr;
|
|
1092
|
+
napi_value reserved_until = nullptr;
|
|
1093
|
+
if (napi_create_object(env, &result) != napi_ok ||
|
|
1094
|
+
napi_create_int64(env, plan.reserve, &reserve) != napi_ok ||
|
|
1095
|
+
napi_create_int64(env, plan.reserved_until, &reserved_until) != napi_ok) {
|
|
1096
|
+
ThrowError(env, "planFreeSpaceProbe: cannot create the result");
|
|
1097
|
+
return nullptr;
|
|
1098
|
+
}
|
|
1099
|
+
SetBoolProperty(env, result, "probe", plan.probe);
|
|
1100
|
+
napi_set_named_property(env, result, "reserve", reserve);
|
|
1101
|
+
napi_set_named_property(env, result, "reservedUntil", reserved_until);
|
|
1037
1102
|
return result;
|
|
1038
1103
|
}
|
|
1039
1104
|
|
|
1105
|
+
struct ExtractedDirectoryWork {
|
|
1106
|
+
std::string directory;
|
|
1107
|
+
int64_t archive_bytes = 0;
|
|
1108
|
+
pushy::delta::Status status{false, ""};
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// checkExtractedDirectory(directory, archiveBytes) -> Promise<void>: walks
|
|
1112
|
+
// the freshly unpacked tree on a libuv worker thread and rejects with the
|
|
1113
|
+
// first violation.
|
|
1114
|
+
napi_value CheckExtractedDirectory(napi_env env, napi_callback_info info) {
|
|
1115
|
+
napi_value args[2] = {nullptr, nullptr};
|
|
1116
|
+
size_t argc = 2;
|
|
1117
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 2) {
|
|
1118
|
+
ThrowError(env, "checkExtractedDirectory: wrong number of arguments");
|
|
1119
|
+
return nullptr;
|
|
1120
|
+
}
|
|
1121
|
+
ExtractedDirectoryWork work;
|
|
1122
|
+
bool ok = false;
|
|
1123
|
+
work.directory = GetString(env, args[0], &ok);
|
|
1124
|
+
if (!ok) {
|
|
1125
|
+
return nullptr;
|
|
1126
|
+
}
|
|
1127
|
+
napi_valuetype type = napi_undefined;
|
|
1128
|
+
if (work.directory.empty() || !GetValueType(env, args[1], &type) ||
|
|
1129
|
+
type != napi_number ||
|
|
1130
|
+
napi_get_value_int64(env, args[1], &work.archive_bytes) != napi_ok) {
|
|
1131
|
+
ThrowError(env, "checkExtractedDirectory: expected a directory and a size");
|
|
1132
|
+
return nullptr;
|
|
1133
|
+
}
|
|
1134
|
+
return QueueWork<ExtractedDirectoryWork>(
|
|
1135
|
+
env,
|
|
1136
|
+
"checkExtractedDirectory",
|
|
1137
|
+
std::move(work),
|
|
1138
|
+
[](ExtractedDirectoryWork& w) {
|
|
1139
|
+
w.status = pushy::archive_limits::CheckExtractedDirectory(
|
|
1140
|
+
w.directory, w.archive_bytes);
|
|
1141
|
+
},
|
|
1142
|
+
[](napi_env cb_env, napi_deferred deferred, const ExtractedDirectoryWork& w) {
|
|
1143
|
+
SettleWithStatus(cb_env, deferred, w.status);
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
bool ExportFunction(
|
|
1148
|
+
napi_env env,
|
|
1149
|
+
napi_value exports,
|
|
1150
|
+
const char* name,
|
|
1151
|
+
napi_callback callback) {
|
|
1152
|
+
napi_value fn = nullptr;
|
|
1153
|
+
if (napi_create_function(env, name, NAPI_AUTO_LENGTH, callback, nullptr, &fn) !=
|
|
1154
|
+
napi_ok) {
|
|
1155
|
+
ThrowError(env, std::string("Unable to create function ") + name);
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (napi_set_named_property(env, exports, name, fn) != napi_ok) {
|
|
1160
|
+
ThrowError(env, std::string("Unable to export function ") + name);
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
} // namespace
|
|
1167
|
+
|
|
1168
|
+
|
|
1040
1169
|
// CRC32(zip/zlib 多项式)。同步:输入是已在内存的 rawfile 资源字节,用于
|
|
1041
1170
|
// pdiff 拷贝前与 __diff.json copiesCrc 比对(不符则整次 patch 失败落 full)。
|
|
1042
1171
|
static napi_value Crc32(napi_env env, napi_callback_info info) {
|
|
@@ -1093,139 +1222,187 @@ static napi_value GetSupportedDiffVersion(napi_env env, napi_callback_info) {
|
|
|
1093
1222
|
return result;
|
|
1094
1223
|
}
|
|
1095
1224
|
|
|
1096
|
-
// ----
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1099
|
-
//
|
|
1225
|
+
// ---- native round (update_flow_core/native_round.h) ----
|
|
1226
|
+
// JSON text in and out. Returning undefined (nullptr without a pending
|
|
1227
|
+
// exception) means the core rejected the input; the ArkTS orchestrator fails
|
|
1228
|
+
// the round.
|
|
1229
|
+
|
|
1230
|
+
// Reads the first `count` arguments (at most 5) into `args`, and those of
|
|
1231
|
+
// them flagged in `strings` (bit i = argument i) as strings into `out[i]`
|
|
1232
|
+
// (null/undefined read as ""). False with an exception pending otherwise.
|
|
1233
|
+
bool GetFlowArgs(napi_env env, napi_callback_info info, const char* name,
|
|
1234
|
+
size_t count, unsigned strings, napi_value* args,
|
|
1235
|
+
std::string* out) {
|
|
1236
|
+
size_t argc = count;
|
|
1237
|
+
if (!GetArgCount(env, info, &argc, args) || argc < count) {
|
|
1238
|
+
ThrowError(env, std::string(name) + ": missing arguments");
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
for (size_t i = 0; i < count; i++) {
|
|
1242
|
+
bool ok = true;
|
|
1243
|
+
if ((strings >> i) & 1u) {
|
|
1244
|
+
out[i] = GetString(env, args[i], &ok);
|
|
1245
|
+
}
|
|
1246
|
+
if (!ok) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1100
1252
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return nullptr;
|
|
1253
|
+
bool GetFlowNumber(napi_env env, napi_value value, const char* name,
|
|
1254
|
+
double* out) {
|
|
1255
|
+
if (napi_get_value_double(env, value, out) != napi_ok) {
|
|
1256
|
+
ThrowError(env, std::string(name) + ": expected number");
|
|
1257
|
+
return false;
|
|
1107
1258
|
}
|
|
1108
|
-
return
|
|
1259
|
+
return true;
|
|
1109
1260
|
}
|
|
1110
1261
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1262
|
+
// The parsed object, or Undefined (which the core rejects).
|
|
1263
|
+
flowjson::Value ParseFlowObject(const std::string& json) {
|
|
1264
|
+
bool ok = false;
|
|
1265
|
+
flowjson::Value value = flowjson::Parse(json, &ok);
|
|
1266
|
+
return ok && value.IsObject() ? value : flowjson::Value::Undefined();
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
napi_value FlowJsonOrUndefined(napi_env env, const flowjson::Value& value) {
|
|
1270
|
+
return value.IsUndefined() ? nullptr
|
|
1271
|
+
: NewString(env, flowjson::Stringify(value));
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// prepareRound(configJson, envJson, randomSample)
|
|
1275
|
+
static napi_value FlowPrepareRound(napi_env env, napi_callback_info info) {
|
|
1276
|
+
napi_value args[3] = {nullptr, nullptr, nullptr};
|
|
1277
|
+
std::string text[3];
|
|
1278
|
+
double sample = 0;
|
|
1279
|
+
if (!GetFlowArgs(env, info, "prepareRound", 3, 0b011, args, text) ||
|
|
1280
|
+
!GetFlowNumber(env, args[2], "prepareRound", &sample)) {
|
|
1117
1281
|
return nullptr;
|
|
1118
1282
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1283
|
+
return FlowJsonOrUndefined(
|
|
1284
|
+
env, flowcore::PrepareRound(text[0], ParseFlowObject(text[1]), sample));
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// nextQueryStep(stateJson, responseText?) — responseText undefined/null for
|
|
1288
|
+
// a failed (or no) request.
|
|
1289
|
+
static napi_value FlowNextQueryStep(napi_env env, napi_callback_info info) {
|
|
1290
|
+
napi_value args[2] = {nullptr, nullptr};
|
|
1291
|
+
std::string text[2];
|
|
1292
|
+
if (!GetFlowArgs(env, info, "nextQueryStep", 2, 0b11, args, text)) {
|
|
1122
1293
|
return nullptr;
|
|
1123
1294
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1295
|
+
return FlowJsonOrUndefined(
|
|
1296
|
+
env, flowcore::NextQueryStep(
|
|
1297
|
+
ParseFlowObject(text[0]),
|
|
1298
|
+
IsNullOrUndefined(env, args[1])
|
|
1299
|
+
? flowjson::Value::Undefined()
|
|
1300
|
+
: flowjson::Value::String(std::move(text[1]))));
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// handleResponse(responseText, configJson, envJson, requestBody,
|
|
1304
|
+
// responseAtSeconds)
|
|
1305
|
+
static napi_value FlowHandleResponse(napi_env env, napi_callback_info info) {
|
|
1306
|
+
napi_value args[5] = {nullptr, nullptr, nullptr, nullptr, nullptr};
|
|
1307
|
+
std::string text[5];
|
|
1308
|
+
double responseAt = 0;
|
|
1309
|
+
if (!GetFlowArgs(env, info, "handleResponse", 5, 0b01111, args, text) ||
|
|
1310
|
+
!GetFlowNumber(env, args[4], "handleResponse", &responseAt)) {
|
|
1127
1311
|
return nullptr;
|
|
1128
1312
|
}
|
|
1129
|
-
return
|
|
1130
|
-
env,
|
|
1313
|
+
return FlowJsonOrUndefined(
|
|
1314
|
+
env, flowcore::HandleRoundResponse(text[0], text[1],
|
|
1315
|
+
ParseFlowObject(text[2]), text[3],
|
|
1316
|
+
responseAt));
|
|
1131
1317
|
}
|
|
1132
1318
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1319
|
+
// ---- persisted native config (update_flow_core/native_config.h) ----
|
|
1320
|
+
// null/undefined arguments read as "" (an absent config): never equivalent,
|
|
1321
|
+
// not_configured at the gate, not an object for the normalizer.
|
|
1322
|
+
|
|
1323
|
+
static napi_value FlowConfigsEquivalent(napi_env env, napi_callback_info info) {
|
|
1135
1324
|
size_t argc = 2;
|
|
1136
1325
|
napi_value args[2] = {nullptr, nullptr};
|
|
1137
1326
|
if (!GetArgCount(env, info, &argc, args) || argc < 2) {
|
|
1138
|
-
ThrowError(env, "
|
|
1327
|
+
ThrowError(env, "configsEquivalent: missing arguments");
|
|
1139
1328
|
return nullptr;
|
|
1140
1329
|
}
|
|
1141
1330
|
bool ok = false;
|
|
1142
|
-
std::string
|
|
1331
|
+
std::string a = GetString(env, args[0], &ok);
|
|
1143
1332
|
if (!ok) {
|
|
1144
1333
|
return nullptr;
|
|
1145
1334
|
}
|
|
1146
|
-
|
|
1147
|
-
if (
|
|
1148
|
-
ThrowError(env, "orderEndpointCandidates: expected number sample");
|
|
1149
|
-
return nullptr;
|
|
1150
|
-
}
|
|
1151
|
-
bool parsed = false;
|
|
1152
|
-
flowjson::Value endpoints = flowjson::Parse(endpoints_json, &parsed);
|
|
1153
|
-
if (!parsed || !endpoints.IsArray()) {
|
|
1335
|
+
std::string b = GetString(env, args[1], &ok);
|
|
1336
|
+
if (!ok) {
|
|
1154
1337
|
return nullptr;
|
|
1155
1338
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1339
|
+
napi_value result = nullptr;
|
|
1340
|
+
napi_get_boolean(env, flowcore::ConfigsEquivalent(a, b), &result);
|
|
1341
|
+
return result;
|
|
1159
1342
|
}
|
|
1160
1343
|
|
|
1161
|
-
static napi_value
|
|
1162
|
-
napi_callback_info info) {
|
|
1344
|
+
static napi_value FlowRunnableConfig(napi_env env, napi_callback_info info) {
|
|
1163
1345
|
size_t argc = 1;
|
|
1164
1346
|
napi_value args[1] = {nullptr};
|
|
1165
1347
|
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
1166
|
-
ThrowError(env, "
|
|
1348
|
+
ThrowError(env, "runnableConfig: missing argument");
|
|
1167
1349
|
return nullptr;
|
|
1168
1350
|
}
|
|
1169
1351
|
bool ok = false;
|
|
1170
|
-
std::string
|
|
1352
|
+
std::string config_json = GetString(env, args[0], &ok);
|
|
1171
1353
|
if (!ok) {
|
|
1172
|
-
// Exception pending (non-string argument): propagate it as a throw
|
|
1173
|
-
// instead of a boolean that the caller would treat as a verdict.
|
|
1174
1354
|
return nullptr;
|
|
1175
1355
|
}
|
|
1176
|
-
|
|
1177
|
-
napi_get_boolean(env, flowcore::IsValidResponse(response_text),
|
|
1178
|
-
&result);
|
|
1179
|
-
return result;
|
|
1356
|
+
return NewString(env, flowcore::RunnableConfigJson(config_json));
|
|
1180
1357
|
}
|
|
1181
1358
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1359
|
+
// Throws Error("Invalid native configuration: ...") for options the host
|
|
1360
|
+
// must fix.
|
|
1361
|
+
static napi_value FlowNormalizeConfiguration(napi_env env,
|
|
1362
|
+
napi_callback_info info) {
|
|
1363
|
+
size_t argc = 1;
|
|
1364
|
+
napi_value args[1] = {nullptr};
|
|
1365
|
+
if (!GetArgCount(env, info, &argc, args) || argc < 1) {
|
|
1366
|
+
ThrowError(env, "normalizeConfiguration: missing argument");
|
|
1188
1367
|
return nullptr;
|
|
1189
1368
|
}
|
|
1190
1369
|
bool ok = false;
|
|
1191
|
-
std::string
|
|
1192
|
-
if (!ok) {
|
|
1193
|
-
return nullptr;
|
|
1194
|
-
}
|
|
1195
|
-
std::string identity_json = GetString(env, args[1], &ok);
|
|
1196
|
-
if (!ok) {
|
|
1197
|
-
return nullptr;
|
|
1198
|
-
}
|
|
1199
|
-
std::string after_download = GetString(env, args[2], &ok);
|
|
1370
|
+
std::string options_json = GetString(env, args[0], &ok);
|
|
1200
1371
|
if (!ok) {
|
|
1201
1372
|
return nullptr;
|
|
1202
1373
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (!
|
|
1374
|
+
std::string config;
|
|
1375
|
+
std::string error;
|
|
1376
|
+
if (!flowcore::NormalizeConfiguration(options_json, &config, &error)) {
|
|
1377
|
+
ThrowError(env, error);
|
|
1206
1378
|
return nullptr;
|
|
1207
1379
|
}
|
|
1208
|
-
return
|
|
1209
|
-
flowjson::Stringify(flowcore::HandleResponse(
|
|
1210
|
-
response_text, identity, false, after_download)));
|
|
1380
|
+
return NewString(env, config);
|
|
1211
1381
|
}
|
|
1212
1382
|
|
|
1213
1383
|
napi_value Init(napi_env env, napi_value exports) {
|
|
1214
1384
|
if (!ExportFunction(env, exports, "syncStateWithBinaryVersion", SyncStateWithBinaryVersion) ||
|
|
1215
1385
|
!ExportFunction(env, exports, "runStateCore", RunStateCore) ||
|
|
1216
|
-
!ExportFunction(env, exports, "
|
|
1217
|
-
!ExportFunction(env, exports, "buildCopyGroups", BuildCopyGroups) ||
|
|
1218
|
-
!ExportFunction(env, exports, "applyDeltaFromSource", ApplyPatchFromFileSource) ||
|
|
1386
|
+
!ExportFunction(env, exports, "applyArchiveDelta", ApplyArchiveDelta) ||
|
|
1219
1387
|
!ExportFunction(env, exports, "cleanupOldEntries", CleanupOldEntries) ||
|
|
1220
|
-
!ExportFunction(env, exports, "sha256Hex", Sha256Hex) ||
|
|
1221
|
-
!ExportFunction(env, exports, "sha256HexFile", Sha256HexFile) ||
|
|
1222
1388
|
!ExportFunction(env, exports, "sha256HexFileAsync", Sha256HexFileAsync) ||
|
|
1389
|
+
!ExportFunction(env, exports, "sha256HexRawFdAsync", Sha256HexRawFdAsync) ||
|
|
1390
|
+
!ExportFunction(env, exports, "isInstallComplete", InstallRecordIsComplete) ||
|
|
1391
|
+
!ExportFunction(env, exports, "recordedBundleSha256", InstallRecordBundleSha256) ||
|
|
1392
|
+
!ExportFunction(env, exports, "sealInstallRecord", SealInstallRecord) ||
|
|
1393
|
+
!ExportFunction(env, exports, "promoteStaging", PromoteStaging) ||
|
|
1394
|
+
!ExportFunction(env, exports, "checkActivation", CheckActivation) ||
|
|
1395
|
+
!ExportFunction(env, exports, "checkUncompressedSize", CheckUncompressedSize) ||
|
|
1396
|
+
!ExportFunction(env, exports, "planFreeSpaceProbe", PlanFreeSpaceProbe) ||
|
|
1397
|
+
!ExportFunction(env, exports, "checkExtractedDirectory", CheckExtractedDirectory) ||
|
|
1223
1398
|
!ExportFunction(env, exports, "crc32", Crc32) ||
|
|
1224
1399
|
!ExportFunction(env, exports, "getSupportedDiffVersion", GetSupportedDiffVersion) ||
|
|
1225
|
-
!ExportFunction(env, exports, "
|
|
1226
|
-
!ExportFunction(env, exports, "
|
|
1227
|
-
!ExportFunction(env, exports, "
|
|
1228
|
-
!ExportFunction(env, exports, "
|
|
1400
|
+
!ExportFunction(env, exports, "prepareRound", FlowPrepareRound) ||
|
|
1401
|
+
!ExportFunction(env, exports, "nextQueryStep", FlowNextQueryStep) ||
|
|
1402
|
+
!ExportFunction(env, exports, "handleResponse", FlowHandleResponse) ||
|
|
1403
|
+
!ExportFunction(env, exports, "configsEquivalent", FlowConfigsEquivalent) ||
|
|
1404
|
+
!ExportFunction(env, exports, "runnableConfig", FlowRunnableConfig) ||
|
|
1405
|
+
!ExportFunction(env, exports, "normalizeConfiguration", FlowNormalizeConfiguration)) {
|
|
1229
1406
|
return nullptr;
|
|
1230
1407
|
}
|
|
1231
1408
|
return exports;
|