已合并
fix: 修复下载pdf时getFilePath非法路径的处理;对@ohos.request的request.downloadFile不支持大写scheme(HTTP://)URL的情况,做规范化处理 #33
fix: 修复下载pdf时getFilePath非法路径的处理;对@ohos.request的request.downloadFile不支持大写scheme(HTTP://)URL的情况,做规范化处理 #33
已合并
mazheng创建于 8月31日
共 55 个文件变更+1038-47
@@ -1,5 +1,12 @@
1-harmony/doc-viewer/BuildProfile.ets1+node_modules
2-harmony/doc-viewer/oh-package-lock.json52+.vscode
3-lib/3+/**/.vscode
4-node_modules/4+bundle.harmony.js
5-package-lock.json5+*.code-workspace
6+**/.DS_Store
7+**/oh-package-lock.json5
8+**/BuildProfile.ets
9+/OHOS_OpenSDK1.0.0/*
10+lib/*
11+oh_modules/*
12+**/oh_modules/*
@@ -0,0 +1,5 @@
1+.idea
2+package-lock.json
3+node_modules
4+OAT.xml
5+example
zhengjinshou
zhengjinshouzhengjinshou9月4日

OAT和example不能忽略

likedislike
@@ -1,3 +1,13 @@
1+# v2.8.1-beta.1
2+ 
3+## Changes
4+ 
5+* Fix: await removeFile before downloadFile to avoid download race condition [#33](https://gitcode.com/CPF-RN/rntpc_react-native-doc-viewer/pull/33)
6+* Fix: handle invalid fileName in getFilePath and normalize URL scheme [#33](https://gitcode.com/CPF-RN/rntpc_react-native-doc-viewer/pull/33)
7+* Refactor: reduce download() nesting depth, add missing semicolons and fix codecheck issues [#33](https://gitcode.com/CPF-RN/rntpc_react-native-doc-viewer/pull/33)
8+* Fix: capture error code in downloadTask fail callback and pass it to JS side [#33](https://gitcode.com/CPF-RN/rntpc_react-native-doc-viewer/pull/33)
9+* Chore: add OAT.xml open source review configuration [#33](https://gitcode.com/CPF-RN/rntpc_react-native-doc-viewer/pull/33)
10+ 
1# v2.7.9-rc.111# v2.7.9-rc.1
2 12 
3## 更新内容13## 更新内容
AOAT.xml+83-0
@@ -0,0 +1,83 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<configuration>
3+ <oatconfig>
4+ <licensefile>LICENSE</licensefile>
5+ <policylist>
6+ <policy name="projectPolicy" desc="">
7+ <policyitem type="license" name="MIT" path=".*" desc="license under the MIT"/>
8+ </policy>
9+ <policy name="projectPolicy" desc="">
10+ <policyitem type="compatibility" name="GPL-2.0+" path=".*" desc="Process that runs independently, invoked by the X process."/>
11+ <policyitem type="compatibility" name="Apache-2.0" path=".*" desc="不影响兼容性"/>
12+ <policyitem type="compatibility" name="BSDStyleLicense" path=".*" desc="不影响兼容性" />
13+ <policyitem type="compatibility" name="MIT" path=".*" desc="不影响兼容性" />
14+ <policyitem type="compatibility" name="GPLStyleLicense" path=".*" desc="不影响兼容性" />
15+ </policy>
16+ </policylist>
17+ <filefilterlist>
18+ <filefilter name="copyrightPolicyFilter" desc="Filters for compatibility,license header policies">
19+ <filteritem type="filepath" name="harmony/doc-viewer/hvigorfile.ts" desc="hvigor构建脚本,DevEco Studio自动生成,不手动修改"/>
20+ <filteritem type="filepath" name="harmony/doc-viewer/src/main/cpp/generated/.*" desc="Codegen生成文件,不手动修改"/>
21+ <filteritem type="filepath" name="harmony/doc-viewer/src/main/ets/generated/.*" desc="Codegen生成文件,不手动修改"/>
22+ <filteritem type="filename" name="*.json5" desc="hvigor配置文件,DevEco Studio自动生成,不手动修改"/>
23+ <filteritem type="filename" name="*/*.json5" desc="hvigor配置文件,DevEco Studio自动生成,不手动修改"/>
24+ <filteritem type="filename" name="*.config.js" desc="工程配置文件,不手动修改"/>
25+ <filteritem type="filename" name=".prettierrc.js" desc="工程配置文件,不手动修改"/>
26+ <filteritem type="filename" name=".yarnrc" desc="工程配置文件,不手动修改"/>
27+ <filteritem type="filename" name=".npmignore" desc="工程配置文件,不手动修改"/>
28+ <filteritem type="filename" name=".ohpmignore" desc="工程配置文件,不手动修改"/>
29+ <filteritem type="filename" name="BuildProfile.ets" desc="工程配置文件,不手动修改"/>
30+ <filteritem type="filename" name="LICENSE" desc="版权文件,不添加版权头"/>
31+ <filteritem type="filename" name="LICENSE.md" desc="版权文件,不添加许可证头"/>
32+ <filteritem type="filename" name="yarn.lock" desc="依赖配置文件,不添加许可证头"/>
33+ <filteritem type="filepath" name="hvigorw" desc="工程模板,不修改版权头,以防有修改版权风险"/>
34+ <filteritem type="filepath" name="hvigorw.bat" desc="工程模板,不修改版权头,以防有修改版权风险"/>
35+ <filteritem type="filepath" name="js/*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
36+ <filteritem type="filepath" name="js/*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
37+ <filteritem type="filepath" name="src/*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
38+ <filteritem type="filepath" name="src/*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
39+ <filteritem type="filepath" name="*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
40+ <filteritem type="filepath" name="*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
41+ <filteritem type="filepath" name="index.*" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
42+ <filteritem type="filepath" name="hvigor/hvigor-wrapper.js" desc="工程模板,不修改版权头,以防有修改版权风险"/>
43+ <filteritem type="filepath" name="hvigor/hvigor-config.json5" desc="工程模板,不修改版权头,以防有修改版权风险"/>
44+ <filteritem type="filename" name="buildEnv.sh" desc="构建环境脚本文件,不修改版权头,以防有修改版权风险"/>
45+ <filteritem type="filepath" name="example/**/*" desc="示例工程,不添加版权头"/>
46+ </filefilter>
47+ <filefilter name="defaultPolicyFilter" desc="Filters for compatibility,license header policies">
48+ <filteritem type="filepath" name="harmony/doc-viewer/hvigorfile.ts" desc="hvigor构建脚本,DevEco Studio自动生成,不手动修改"/>
49+ <filteritem type="filepath" name="harmony/doc-viewer/src/main/cpp/generated/.*" desc="Codegen生成文件,不手动修改"/>
50+ <filteritem type="filepath" name="harmony/doc-viewer/src/main/ets/generated/.*" desc="Codegen生成文件,不手动修改"/>
51+ <filteritem type="filename" name="*.json5" desc="hvigor配置文件,DevEco Studio自动生成,不手动修改"/>
52+ <filteritem type="filename" name="*/*.json5" desc="hvigor配置文件,DevEco Studio自动生成,不手动修改"/>
53+ <filteritem type="filename" name="*.config.js" desc="工程配置文件,不手动修改"/>
54+ <filteritem type="filename" name=".prettierrc.js" desc="工程配置文件,不手动修改"/>
55+ <filteritem type="filename" name=".yarnrc" desc="工程配置文件,不手动修改"/>
56+ <filteritem type="filename" name=".npmignore" desc="工程配置文件,不手动修改"/>
57+ <filteritem type="filename" name=".ohpmignore" desc="工程配置文件,不手动修改"/>
58+ <filteritem type="filename" name="BuildProfile.ets" desc="工程配置文件,不手动修改"/>
59+ <filteritem type="filename" name="LICENSE" desc="版权文件,不添加许可证头"/>
60+ <filteritem type="filename" name="LICENSE.md" desc="版权文件,不添加许可证头"/>
61+ <filteritem type="filename" name="yarn.lock" desc="依赖配置文件,不添加许可证头"/>
62+ <filteritem type="filepath" name="hvigorw" desc="工程模板,不添加许可证头"/>
63+ <filteritem type="filepath" name="hvigorw.bat" desc="工程模板,不添加许可证头"/>
64+ <filteritem type="filepath" name="js/*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
65+ <filteritem type="filepath" name="js/*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
66+ <filteritem type="filepath" name="src/*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
67+ <filteritem type="filepath" name="src/*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
68+ <filteritem type="filepath" name="*.js" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
69+ <filteritem type="filepath" name="*.ts" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
70+ <filteritem type="filepath" name="index.*" desc="第三方开源软件源码,不修改版权头,以防有修改版权风险"/>
71+ <filteritem type="filepath" name="hvigor/hvigor-wrapper.js" desc="工程模板,不修改版权头,以防有修改版权风险"/>
72+ <filteritem type="filepath" name="hvigor/hvigor-config.json5" desc="工程模板,不修改版权头,以防有修改版权风险"/>
73+ <filteritem type="filename" name="buildEnv.sh" desc="构建环境脚本文件,不修改版权头,以防有修改版权风险"/>
74+ <filteritem type="filepath" name="example/**/*" desc="示例工程,不添加版权头"/>
75+ </filefilter>
76+ 
77+ <filefilter name="binaryFileTypePolicyFilter" desc="Filters for copyright header policies">
78+ <filteritem type="filepath" name="harmony/doc_viewer.har" desc="项目打包文件"/>
79+ <filteritem type="filepath" name="example/**/*" desc="示例工程,不添加版权头"/>
80+ </filefilter>
81+ </filefilterlist>
82+ </oatconfig>
83+</configuration>
@@ -0,0 +1,4 @@
1+#!/bin/bash
2+BUILD_PKG_DIR=example
3+BUILD_SAMPLE_DIR=example/harmony
4+BUILD_THIRD_PARTY_DIR=harmony
@@ -0,0 +1,13 @@
1+{
2+ "extends": "@react-native",
3+ "rules": {
4+ "react-native/no-inline-styles": "off",
5+ "react/no-unstable-nested-components": "off",
6+ "react/react-in-jsx-scope": "off",
7+ "@typescript-eslint/no-unused-vars": "warn",
8+ "react-hooks/exhaustive-deps": "off",
9+ "radix": "off",
10+ "prettier/prettier": "warn",
11+ "max-lines": "off"
12+ }
13+}
@@ -0,0 +1,71 @@
1+.husky
2+bundle.harmony.js
3+package-lock.json
4+*.hbc
5+lintCppResult.txt
6+ 
7+# ---
8+ 
9+# OSX
10+#
11+.DS_Store
12+ 
13+# Xcode
14+#
15+build/
16+*.pbxuser
17+!default.pbxuser
18+*.mode1v3
19+!default.mode1v3
20+*.mode2v3
21+!default.mode2v3
22+*.perspectivev3
23+!default.perspectivev3
24+xcuserdata
25+*.xccheckout
26+*.moved-aside
27+DerivedData
28+*.hmap
29+*.ipa
30+*.xcuserstate
31+ 
32+# Android/IntelliJ
33+#
34+build/
35+.idea
36+.gradle
37+local.properties
38+*.iml
39+*.hprof
40+.cxx/
41+*.keystore
42+!debug.keystore
43+BuildProfile.ets
44+ 
45+# node.js
46+#
47+node_modules/
48+npm-debug.log
49+yarn-error.log
50+ 
51+# fastlane
52+#
53+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
54+# screenshots whenever they are needed.
55+# For more information about the recommended setup visit:
56+# https://docs.fastlane.tools/best-practices/source-control/
57+ 
58+**/fastlane/report.xml
59+**/fastlane/Preview.html
60+**/fastlane/screenshots
61+**/fastlane/test_output
62+ 
63+# Bundle artifact
64+*.jsbundle
65+ 
66+# Ruby / CocoaPods
67+/vendor/bundle/
68+ 
69+# Temporary files created by Metro to check the health of the file watcher
70+.metro-health-check*
71+*.htrace
@@ -0,0 +1 @@
1+18
@@ -0,0 +1,7 @@
1+module.exports = {
2+ arrowParens: 'avoid',
3+ bracketSameLine: true,
4+ bracketSpacing: true,
5+ singleQuote: true,
6+ trailingComma: 'all',
7+};
@@ -0,0 +1 @@
1+{}
@@ -0,0 +1,4 @@
1+{
2+ "name": "app_name",
3+ "displayName": "tester"
4+}
@@ -0,0 +1,5 @@
1+module.exports = {
2+ presets: ['module:@react-native/babel-preset'],
3+ plugins: [
4+ ],
5+};
@@ -0,0 +1,24 @@
1+# it may cause some issues when building the project when switching branches
2+package-lock.json
3+# we add this because we want to keep the signing configs out of git
4+/build-profile.json5
5+ 
6+rnoh_modules
7+ 
8+**/oh-package-lock.json5
9+# ---
10+ 
11+/node_modules
12+/local.properties
13+/.idea
14+**/build
15+/.hvigor
16+.cxx
17+/.clangd
18+/.clang-format
19+/.clang-tidy
20+/oh_modules
21+hvigorw
22+hvigorw.bat
23+hvigor/hvigor-wrapper.js
24+ 
@@ -0,0 +1,10 @@
1+{
2+ "app": {
3+ "bundleName": "com.harmony.doc.viewer.demo",
4+ "vendor": "example",
5+ "versionCode": 1000000,
6+ "versionName": "1.0.0",
7+ "icon": "$media:app_icon",
8+ "label": "$string:app_name"
9+ }
10+}
@@ -0,0 +1,8 @@
1+{
2+ "string": [
3+ {
4+ "name": "app_name",
5+ "value": "Tester"
6+ }
7+ ]
8+}
@@ -0,0 +1,39 @@
1+{
2+ "app": {
3+ "products": [
4+ {
5+ "name": 'default',
6+ "signingConfig": 'default',
7+ "compatibleSdkVersion": '5.0.0(12)',
8+ "runtimeOS": 'HarmonyOS'
9+ },
10+ ],
11+ "buildModeSet": [
12+ {
13+ "name": 'debug',
14+ },
15+ {
16+ "name": 'release',
17+ },
18+ ],
19+ "signingConfigs": []
20+ },
21+ "modules": [
22+ {
23+ "name": 'entry',
24+ "srcPath": './entry',
25+ "targets": [
26+ {
27+ "name": 'default',
28+ "applyToProducts": [
29+ 'default'
30+ ],
31+ },
32+ ],
33+ },
34+ {
35+ "name": "doc_viewer",
36+ "srcPath": '../../harmony/doc-viewer'
37+ }
38+ ],
39+}
@@ -0,0 +1,32 @@
1+{
2+ "files": ["**/*.ts", "**/*.ets"],
3+ "ignore": [
4+ "**/ohosTest/**/*",
5+ "**/node_modules/**/*",
6+ "**/hvigorfile.ts",
7+ "**/node_modules/**/*",
8+ "**/oh_modules/**/*",
9+ "**/build/**/*",
10+ "**/.preview/**/*"
11+ ],
12+ "plugins": ["@typescript-eslint"],
13+ "ruleSet": [],
14+ "rules": {
15+ "@typescript-eslint/await-thenable": "warn",
16+ "@typescript-eslint/consistent-type-imports": "warn",
17+ "@typescript-eslint/explicit-function-return-type": "warn",
18+ "@typescript-eslint/explicit-module-boundary-types": "warn",
19+ "@typescript-eslint/no-dynamic-delete": "warn",
20+ "@typescript-eslint/no-explicit-any": "warn",
21+ "@typescript-eslint/no-for-in-array": "warn",
22+ "@typescript-eslint/no-this-alias": "warn",
23+ "@typescript-eslint/no-unnecessary-type-constraint": "warn",
24+ "@typescript-eslint/no-unsafe-argument": "warn",
25+ "@typescript-eslint/no-unsafe-assignment": "warn",
26+ "@typescript-eslint/no-unsafe-call": "warn",
27+ "@typescript-eslint/no-unsafe-member-access": "warn",
28+ "@typescript-eslint/no-unsafe-return": "warn",
29+ "@typescript-eslint/prefer-literal-enum-member": "warn"
30+ },
31+ "overrides": []
32+}
@@ -0,0 +1,7 @@
1+/node_modules
2+/.preview
3+/build
4+/.cxx
5+package-lock.json
6+/oh_modules
7+/assets
@@ -0,0 +1,22 @@
1+{
2+ "apiType": 'stageMode',
3+ "buildOption": {
4+ "externalNativeOptions": {
5+ "path": "./src/main/cpp/CMakeLists.txt",
6+ "arguments": "",
7+ "cppFlags": "-s",
8+ "abiFilters": [
9+ "arm64-v8a",
10+ "x86_64"
11+ ]
12+ },
13+ },
14+ "targets": [
15+ {
16+ "name": "default",
17+ },
18+ {
19+ "name": "ohosTest",
20+ }
21+ ]
22+}
@@ -0,0 +1,2 @@
1+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
2+export { hapTasks } from '@ohos/hvigor-ohos-plugin';
@@ -0,0 +1,11 @@
1+{
2+ "license": "ISC",
3+ "devDependencies": {},
4+ "name": "entry",
5+ "description": "example description",
6+ "version": "1.0.0",
7+ "dependencies": {
8+ "@rnoh/react-native-openharmony": "file:../../node_modules/react-native-harmony/react_native_openharmony.har",
9+ "@react-native-ohos/react-native-doc-viewer": "file:../../node_modules/@react-native-ohos/react-native-doc-viewer/harmony/doc_viewer.har"
10+ }
11+}
@@ -0,0 +1,2 @@
1+jsbundle.h
2+generated/
@@ -0,0 +1,41 @@
1+project(rnapp)
2+cmake_minimum_required(VERSION 3.4.1)
3+set(CMAKE_SKIP_BUILD_RPATH TRUE)
4+set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
5+set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
6+set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
7+set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules/@rnoh/react-native-openharmony/src/main/cpp")
8+set(RNOH_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
9+set(LOG_VERBOSITY_LEVEL 1)
10+set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
11+set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
12+set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
13+ 
14+set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
15+add_compile_definitions(WITH_HITRACE_SYSTRACE)
16+ 
17+# (VM) Define a variable and assign it to the current module's cpp directory
18+set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
19+ 
20+# Add the Header File directory, including cpp, cpp/include, and tell cmake to find the Header Files introduced by the code here
21+include_directories(${NATIVERENDER_ROOT_PATH}
22+ ${NATIVERENDER_ROOT_PATH}/include)
23+ 
24+add_subdirectory("${RNOH_CPP_DIR}" ./rn)
25+ 
26+# RNOH_BEGIN: manual_package_linking_1
27+add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-doc-viewer/src/main/cpp" ./doc-viewer)
28+# RNOH_END: manual_package_linking_1
29+ 
30+file(GLOB GENERATED_CPP_FILES "${CMAKE_CURRENT_SOURCE_DIR}/generated/*.cpp") # this line is needed by codegen v1
31+ 
32+add_library(rnoh_app SHARED
33+ ${GENERATED_CPP_FILES}
34+ "./PackageProvider.cpp"
35+ "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
36+)
37+target_link_libraries(rnoh_app PUBLIC rnoh)
38+ 
39+# RNOH_BEGIN: manual_package_linking_2
40+target_link_libraries(rnoh_app PUBLIC rnoh_doc_viewer)
41+# RNOH_END: manual_package_linking_2
@@ -0,0 +1,13 @@
1+#include "RNOH/PackageProvider.h"
2+#include "generated/RNOHGeneratedPackage.h"
3+#include "DocViewerPackage.h"
4+ 
5+using namespace rnoh;
6+ 
7+std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx)
8+{
9+ return {
10+ std::make_shared<RNOHGeneratedPackage>(ctx), // generated by codegen v1
11+ std::make_shared<DocViewerPackage>(ctx)
12+ };
13+}
@@ -0,0 +1,6 @@
1+import type {RNPackageContext, RNPackage} from '@rnoh/react-native-openharmony/ts';
2+import {DocViewPackage} from '@react-native-ohos/react-native-doc-viewer/ts';
3+ 
4+export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
5+ return [new DocViewPackage(ctx)];
6+}
Binary files do not support preview
@@ -0,0 +1,15 @@
1+import {RNAbility} from '@rnoh/react-native-openharmony';
2+import {AbilityConstant, Want} from '@kit.AbilityKit';
3+ 
4+export default class EntryAbility extends RNAbility {
5+ onCreate(want: Want) {
6+ super.onCreate(want)
7+ }
8+ 
9+ getPagePath() {
10+ return 'pages/Index';
11+ }
12+ 
13+ onNewWant(want: Want, _launchParam: AbilityConstant.LaunchParam): void {
14+ }
15+}
@@ -0,0 +1,106 @@
1+import {
2+ AnyJSBundleProvider,
3+ ComponentBuilderContext,
4+ FileJSBundleProvider,
5+ MetroJSBundleProvider,
6+ ResourceJSBundleProvider,
7+ RNApp,
8+ RNOHErrorDialog,
9+ RNOHLogger,
10+ TraceJSBundleProviderDecorator,
11+ RNOHCoreContext
12+} from '@rnoh/react-native-openharmony';
13+import font from '@ohos.font';
14+import { createRNPackages } from '../RNPackagesFactory';
15+ 
16+const arkTsComponentNames: Array<string> = [
17+];
18+ 
19+@Builder
20+export function buildCustomRNComponent(ctx: ComponentBuilderContext) {
21+ Stack() {
22+ }
23+ .position({x: 0, y: 0})
24+}
25+ 
26+const wrappedCustomRNComponentBuilder = wrapBuilder(buildCustomRNComponent)
27+ 
28+/**
29+ * If you want to use custom fonts, you need to register them here.
30+ * We should support react-native-asset to handle registering fonts automatically.
31+ */
32+const fonts: font.FontOptions[] = [
33+ {
34+ familyName: 'Pacifico-Regular',
35+ familySrc: '/assets/fonts/Pacifico-Regular.ttf'
36+ },
37+ {
38+ familyName: 'StintUltraCondensed-Regular',
39+ familySrc: '/assets/fonts/StintUltraCondensed-Regular.ttf'
40+ }
41+]
42+ 
43+@Entry
44+@Component
45+struct Index {
46+ @StorageLink('RNOHCoreContext') private rnohCoreContext: RNOHCoreContext | undefined = undefined
47+ @State shouldShow: boolean = false
48+ private logger!: RNOHLogger
49+ 
50+ aboutToAppear() {
51+ this.logger = this.rnohCoreContext!.logger.clone("Index")
52+ const stopTracing = this.logger.clone("aboutToAppear").startTracing()
53+ for (const customFont of fonts) {
54+ font.registerFont(customFont)
55+ }
56+ 
57+ this.shouldShow = true
58+ stopTracing()
59+ }
60+ 
61+ onBackPress(): boolean | undefined {
62+ // NOTE: this is required since `Ability`'s `onBackPressed` function always
63+ // terminates or puts the app in the background, but we want Ark to ignore it completely
64+ // when handled by RN
65+ this.rnohCoreContext!.dispatchBackPress()
66+ return true
67+ }
68+ 
69+ build() {
70+ Column() {
71+ if (this.rnohCoreContext && this.shouldShow) {
72+ if (this.rnohCoreContext?.isDebugModeEnabled) {
73+ RNOHErrorDialog({ ctx: this.rnohCoreContext })
74+ }
75+ RNApp({
76+ rnInstanceConfig: {
77+ createRNPackages,
78+ enableNDKTextMeasuring: true,
79+ enableBackgroundExecutor: false,
80+ enableCAPIArchitecture: true,
81+ arkTsComponentNames: arkTsComponentNames,
82+ },
83+ initialProps: { "foo": "bar" } as Record<string, string>,
84+ appKey: "app_name",
85+ wrappedCustomRNComponentBuilder: wrappedCustomRNComponentBuilder,
86+ onSetUp: (rnInstance) => {
87+ rnInstance.enableFeatureFlag("ENABLE_RN_INSTANCE_CLEAN_UP")
88+ },
89+ jsBundleProvider: new TraceJSBundleProviderDecorator(
90+ new AnyJSBundleProvider([
91+ new MetroJSBundleProvider(),
92+ // NOTE: to load the bundle from file, place it in
93+ // `/data/app/el2/100/base/com.rnoh.tester/files/bundle.harmony.js`
94+ // on your device. The path mismatch is due to app sandboxing on HarmonyOS
95+ new FileJSBundleProvider('/data/storage/el2/base/files/bundle.harmony.js'),
96+ new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'hermes_bundle.hbc'),
97+ new ResourceJSBundleProvider(this.rnohCoreContext.uiAbilityContext.resourceManager, 'bundle.harmony.js')
98+ ]),
99+ this.rnohCoreContext.logger),
100+ })
101+ }
102+ }
103+ .height('100%')
104+ .width('100%')
105+ }
106+}
@@ -0,0 +1,47 @@
1+{
2+ "module": {
3+ "name": "entry",
4+ "type": "entry",
5+ "description": "$string:module_desc",
6+ "mainElement": "EntryAbility",
7+ "deviceTypes": [
8+ "default"
9+ ],
10+ "deliveryWithInstall": true,
11+ "installationFree": false,
12+ "pages": "$profile:main_pages",
13+ "requestPermissions": [
14+ {
15+ "name": "ohos.permission.INTERNET"
16+ },
17+ ],
18+ "metadata": [
19+ {
20+ "name": "OPTLazyForEach",
21+ "value": "true",
22+ }
23+ ],
24+ "abilities": [
25+ {
26+ "name": "EntryAbility",
27+ "srcEntry": "./ets/entryability/EntryAbility.ets",
28+ "description": "$string:EntryAbility_desc",
29+ "icon": "$media:icon",
30+ "label": "$string:EntryAbility_label",
31+ "startWindowIcon": "$media:icon",
32+ "startWindowBackground": "$color:start_window_background",
33+ "visible": true,
34+ "skills": [
35+ {
36+ "entities": [
37+ "entity.system.home"
38+ ],
39+ "actions": [
40+ "action.system.home"
41+ ]
42+ }
43+ ]
44+ }
45+ ]
46+ }
47+}
@@ -0,0 +1 @@
1+rawfile/assets
@@ -0,0 +1,8 @@
1+{
2+ "color": [
3+ {
4+ "name": "start_window_background",
5+ "value": "#FFFFFF"
6+ }
7+ ]
8+}
@@ -0,0 +1,16 @@
1+{
2+ "string": [
3+ {
4+ "name": "module_desc",
5+ "value": "module description"
6+ },
7+ {
8+ "name": "EntryAbility_desc",
9+ "value": "description"
10+ },
11+ {
12+ "name": "EntryAbility_label",
13+ "value": "RN Tester"
14+ }
15+ ]
16+}
@@ -0,0 +1,5 @@
1+{
2+ "src": [
3+ "pages/Index"
4+ ]
5+}
@@ -0,0 +1,12 @@
1+$directoryPath = Split-Path -Parent $MyInvocation.MyCommand.Path
2+$filePaths = Get-ChildItem $directoryPath -Recurse -Include *.h, *.cpp |
3+Where-Object {
4+ $_.DirectoryName -notmatch 'third-party' -and
5+ $_.DirectoryName -notmatch 'patches' -and
6+ $_.DirectoryName -notmatch 'node_modules' -and
7+ $_.DirectoryName -notmatch '.cxx' -and
8+ $_.DirectoryName -notmatch 'build'
9+}
10+foreach ($filePath in $filePaths) {
11+ & "clang-format.exe" -style=file -i $filePath.FullName
12+}
@@ -0,0 +1 @@
1+*.tgz
@@ -0,0 +1,21 @@
1+{
2+ "modelVersion": "5.0.0",
3+ "dependencies": {
4+ },
5+ "execution": {
6+ // "analyze": "default", /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */
7+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
8+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
9+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
10+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
11+ },
12+ "logging": {
13+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
14+ },
15+ "debugging": {
16+ "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
17+ },
18+ "nodeOptions": {
19+ // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */
20+ }
21+}
@@ -0,0 +1,2 @@
1+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
2+export { appTasks } from '@ohos/hvigor-ohos-plugin';
@@ -0,0 +1,12 @@
1+{
2+ "modelVersion": "5.0.0",
3+ "license": "ISC",
4+ "name": "rnoh",
5+ "description": "example description",
6+ "repository": {},
7+ "version": "1.0.0",
8+ "overrides": {
9+ "@rnoh/react-native-openharmony": "file:./../node_modules/react-native-harmony/react_native_openharmony.har"
10+ },
11+ "dynamicDependencies": {}
12+}
@@ -0,0 +1,5 @@
1+import {AppRegistry} from 'react-native';
2+import {name as appName} from './app.json';
3+import DocViewerExample from './src/DocViewerTest';
4+ 
5+AppRegistry.registerComponent(appName, () => DocViewerExample);
@@ -0,0 +1,5 @@
1+/** @type {import('ts-jest').JestConfigWithTsJest} */
2+module.exports = {
3+ preset: 'ts-jest',
4+ testEnvironment: 'node',
5+};
@@ -0,0 +1,24 @@
1+const {mergeConfig, getDefaultConfig} = require('@react-native/metro-config');
2+const {createHarmonyMetroConfig} = require('react-native-harmony/metro.config');
3+ 
4+/**
5+ * @type {import("metro-config").ConfigT}
6+ */
7+const config = {
8+ transformer: {
9+ getTransformOptions: async () => ({
10+ transform: {
11+ experimentalImportSupport: false,
12+ inlineRequires: true,
13+ },
14+ }),
15+ },
16+};
17+ 
18+module.exports = mergeConfig(
19+ getDefaultConfig(__dirname),
20+ createHarmonyMetroConfig({
21+ reactNativeHarmonyPackageName: 'react-native-harmony',
22+ }),
23+ config,
24+);
@@ -0,0 +1,58 @@
1+{
2+ "name": "react-native-harmony-tester",
3+ "version": "1.0.0",
4+ "private": true,
5+ "scripts": {
6+ "i": "cd ../ && npm i && npm run pack && cd ./example && npm i",
7+ "reStart": "npm run install:pkg && npm run codegen && hdc rport tcp:8081 tcp:8081 && react-native start",
8+ "start": "hdc rport tcp:8081 tcp:8081 && react-native start",
9+ "codegen": "react-native codegen-harmony --rnoh-module-path ./harmony/entry/oh_modules/@rnoh/react-native-openharmony",
10+ "pack:pkg": "cd ../ && npm pack && cd ./example",
11+ "install:pkg": "npm uninstall @react-native-ohos/react-native-doc-viewer && npm run pack:pkg && npm i @react-native-ohos/react-native-doc-viewer@file:../react-native-ohos-react-native-doc-viewer-2.8.1-beta.1.tgz",
12+ "dev": "npm run codegen && react-native bundle-harmony --dev --minify=false",
13+ "prod": "npm run codegen && react-native bundle-harmony --dev=false --minify=true",
14+ "postinstall": "node ./scripts/create-build-profile",
15+ "fast-install-project": "cd ../ && npm i --force && npm pack && rm -r node_modules && cd ./example && npm i --force",
16+ "fast:pkg": "npm run fast-install-project && npm run dev"
17+ },
18+ "dependencies": {
19+ "@react-native-oh/react-native-harmony": "0.77.18-1",
20+ "@react-native-ohos/react-native-doc-viewer": "file:../react-native-ohos-react-native-doc-viewer-2.8.1-beta.1.tgz",
21+ "react": "18.3.1",
22+ "react-native": "0.77.1",
23+ "react-native-harmony": "npm:@react-native-oh/react-native-harmony@0.77.18-1"
24+ },
25+ "devDependencies": {
26+ "@babel/core": "^7.25.2",
27+ "@babel/preset-env": "^7.25.3",
28+ "@babel/runtime": "^7.25.0",
29+ "@react-native-community/cli": "15.0.1",
30+ "@react-native-community/eslint-config": "^3.2.0",
31+ "@react-native-oh/react-native-harmony-cli": "0.77.18-1",
32+ "@react-native/babel-preset": "0.77.1",
33+ "@react-native/eslint-config": "0.77.1",
34+ "@react-native/metro-config": "^0.77.1",
35+ "@react-native/typescript-config": "0.77.1",
36+ "@rnoh/react-native-harmony-cli": "npm:@react-native-oh/react-native-harmony-cli@0.77.18-1",
37+ "@tsconfig/react-native": "^3.0.0",
38+ "@types/jest": "^29.5.13",
39+ "@types/react": "^18.2.6",
40+ "@types/react-test-renderer": "^18.0.0",
41+ "babel-jest": "^29.6.3",
42+ "eslint": "^8.19.0",
43+ "jest": "^29.7.0",
44+ "metro-react-native-babel-preset": "0.76.8",
45+ "prettier": "2.8.8",
46+ "react-test-renderer": "18.3.1",
47+ "typescript": "5.5.4"
48+ },
49+ "overrides": {
50+ "@react-native-community/cli": "15.0.1"
51+ },
52+ "resolutions": {
53+ "@react-native-community/cli": "15.0.1"
54+ },
55+ "engines": {
56+ "node": ">=18"
57+ }
58+}
@@ -0,0 +1,5 @@
1+module.exports = {
2+ project: {
3+ },
4+ assets: ['./assets/fonts/'],
5+};
@@ -0,0 +1,39 @@
1+const fs = require('fs');
2+const JSON5 = require('json5');
3+const path = require('path');
4+ 
5+const templatePath = path.join(
6+ __dirname,
7+ '..',
8+ 'harmony',
9+ 'build-profile.template.json5',
10+);
11+const existingProfilePath = path.join(
12+ __dirname,
13+ '..',
14+ 'harmony',
15+ 'build-profile.json5',
16+);
17+ 
18+if (fs.existsSync(existingProfilePath)) {
19+ let existingProfile = JSON5.parse(
20+ fs.readFileSync(existingProfilePath, 'utf-8'),
21+ );
22+ let template = JSON5.parse(fs.readFileSync(templatePath, 'utf-8'));
23+ let signingConfigs =
24+ existingProfile.app && existingProfile.app.signingConfigs;
25+ 
26+ existingProfile = {...template};
27+ 
28+ if (signingConfigs) {
29+ existingProfile.app.signingConfigs = signingConfigs;
30+ }
31+ 
32+ fs.writeFileSync(
33+ existingProfilePath,
34+ JSON5.stringify(existingProfile, null, 2),
35+ );
36+} else {
37+ // File doesn't exist, create a copy from the template
38+ fs.copyFileSync(templatePath, existingProfilePath);
39+}
@@ -0,0 +1,140 @@
1+import React from 'react';
2+import {ScrollView, StyleSheet, View, Button, Text} from 'react-native';
3+import OpenFile from '@react-native-ohos/react-native-doc-viewer';
4+ 
5+interface FileInfo {
6+ url?: string;
7+ fileName?: string;
8+ fileType?: string;
9+ cache?: boolean;
10+ base64?: string;
11+}
12+ 
13+const testUrls = {
14+ pdf: 'https://filesamples.com/samples/document/pdf/sample3.pdf',
15+ mp3: 'https://filesamples.com/samples/audio/mp3/sample3.mp3',
16+ mp4: 'https://filesamples.com/samples/video/mp4/sample_960x400_ocean_with_audio.mp4',
17+ doc: 'https://filesamples.com/samples/document/doc/sample1.doc',
18+ jpeg: 'https://filesamples.com/samples/image/jpeg/sample_1280x853.jpeg',
19+ xlsx: 'https://filesamples.com/samples/document/xlsx/sample1.xlsx',
20+};
21+ 
22+const base64Png =
23+ 'iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA4pOS9AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVQoz2P8z0AZYKQWGBgYAMXeAT3H9Lh1AAAAAElFTkSuQmCC';
24+ 
25+export function DocViewerExample() {
26+ const [status, setStatus] = React.useState('idle');
27+ 
28+ const openDoc = (config: FileInfo) => {
29+ setStatus(`openDoc ${config.fileName ?? ''}`);
30+ OpenFile.openDoc([config], (err: string) => {
31+ setStatus(err ? `openDoc err: ${err}` : 'openDoc success');
32+ });
33+ };
34+ 
35+ const openDocBinaryinUrl = (config: FileInfo) => {
36+ setStatus(`openDocBinaryinUrl ${config.fileName ?? ''}`);
37+ OpenFile.openDocBinaryinUrl([config], (err: string) => {
38+ setStatus(err ? `openDocBinaryinUrl err: ${err}` : 'openDocBinaryinUrl success');
39+ });
40+ };
41+ 
42+ const openDocb64 = (config: FileInfo) => {
43+ setStatus('openDocb64');
44+ OpenFile.openDocb64([config], (err: string) => {
45+ setStatus(err ? `openDocb64 err: ${err}` : 'openDocb64 success');
46+ });
47+ };
48+ 
49+ return (
50+ <View style={styles.container}>
51+ <Text style={styles.title}>react-native-doc-viewer tester</Text>
52+ <Text style={styles.status}>status: {status}</Text>
53+ <ScrollView>
54+ <View style={styles.item}>
55+ <Button
56+ title="Open PDF with openDoc"
57+ onPress={() =>
58+ openDoc({url: testUrls.pdf, fileName: 'sample.pdf', fileType: 'pdf', cache: true})
59+ }
60+ />
61+ </View>
62+ <View style={styles.item}>
63+ <Button
64+ title="Open PDF with openDoc (no cache)"
65+ onPress={() =>
66+ openDoc({url: testUrls.pdf, fileName: 'sample.pdf', fileType: 'pdf', cache: false})
67+ }
68+ />
69+ </View>
70+ <View style={styles.item}>
71+ <Button
72+ title="Open MP4 with openDoc"
73+ onPress={() =>
74+ openDoc({url: testUrls.mp4, fileName: 'sample.mp4', fileType: 'mp4', cache: true})
75+ }
76+ />
77+ </View>
78+ <View style={styles.item}>
79+ <Button
80+ title="Open MP3 with openDoc"
81+ onPress={() =>
82+ openDoc({url: testUrls.mp3, fileName: 'sample.mp3', fileType: 'mp3', cache: true})
83+ }
84+ />
85+ </View>
86+ <View style={styles.item}>
87+ <Button
88+ title="Open DOC with openDoc"
89+ onPress={() =>
90+ openDoc({url: testUrls.doc, fileName: 'sample.doc', fileType: 'doc', cache: true})
91+ }
92+ />
93+ </View>
94+ <View style={styles.item}>
95+ <Button
96+ title="Open XLSX with openDoc"
97+ onPress={() =>
98+ openDoc({url: testUrls.xlsx, fileName: 'sample.xlsx', fileType: 'xlsx', cache: true})
99+ }
100+ />
101+ </View>
102+ <View style={styles.item}>
103+ <Button
104+ title="Open JPEG with openDocBinaryinUrl"
105+ onPress={() =>
106+ openDocBinaryinUrl({
107+ url: testUrls.jpeg,
108+ fileName: 'sample.jpeg',
109+ fileType: 'jpeg',
110+ cache: true,
111+ })
112+ }
113+ />
114+ </View>
115+ <View style={styles.item}>
116+ <Button
117+ title="Open base64 PNG with openDocb64"
118+ onPress={() =>
119+ openDocb64({
120+ base64: base64Png,
121+ fileName: 'sample.png',
122+ fileType: 'png',
123+ cache: false,
124+ })
125+ }
126+ />
127+ </View>
128+ </ScrollView>
129+ </View>
130+ );
131+}
132+ 
133+const styles = StyleSheet.create({
134+ container: {flex: 1, backgroundColor: '#fff', padding: 16},
135+ title: {fontSize: 18, fontWeight: 'bold', marginBottom: 8},
136+ status: {fontSize: 12, color: '#666', marginBottom: 12},
137+ item: {marginBottom: 12},
138+});
139+ 
140+export default DocViewerExample;
@@ -0,0 +1,15 @@
1+{
2+ "extends": "@react-native/typescript-config/tsconfig.json",
3+ "compilerOptions": {
4+ "jsx": "react-native",
5+ "paths": {
6+ "react-native": [
7+ "./node_modules/react-native-harmony"
8+ ],
9+ },
10+ "skipLibCheck": true,
11+ },
12+ "exclude": [
13+ "harmony"
14+ ]
15+}
@@ -1,5 +1,5 @@
1export default class BuildProfile {1export default class BuildProfile {
2- static readonly HAR_VERSION = '2.7.9-rc.1';2+ static readonly HAR_VERSION = '2.8.1-beta.1';
3 static readonly BUILD_MODE_NAME = 'debug';3 static readonly BUILD_MODE_NAME = 'debug';
4 static readonly DEBUG = true;4 static readonly DEBUG = true;
5 static readonly TARGET_NAME = 'default';5 static readonly TARGET_NAME = 'default';
@@ -1,6 +1,6 @@
1{1{
2 "name": "@react-native-ohos/react-native-doc-viewer",2 "name": "@react-native-ohos/react-native-doc-viewer",
3- "version": "2.8.0",3+ "version": "2.8.1-beta.1",
4 type: 'module',4 type: 'module',
5 dependencies: {5 dependencies: {
6 "@rnoh/react-native-openharmony": 'file:../react_native_openharmony'6 "@rnoh/react-native-openharmony": 'file:../react_native_openharmony'
@@ -75,6 +75,6 @@ const mimeMap = {
75}75}
76 76 
77export function getMimeType(fileType: string) {77export function getMimeType(fileType: string) {
78- fileType = fileType.toLowerCase()78+ fileType = typeof fileType === 'string' ? fileType.toLowerCase() : ''
79 return mimeMap[fileType] || mimeMap['pdf']79 return mimeMap[fileType] || mimeMap['pdf']
80}80}
@@ -46,6 +46,10 @@ export class DocViewTurboModule extends TurboModule{
46 if (base64 && fileName && fileType) {46 if (base64 && fileName && fileType) {
47 try{47 try{
48 const filePath = this.getFilePath(fileName)48 const filePath = this.getFilePath(fileName)
49+ if (!filePath) {
50+ callback(`invalid fileName:${fileName}`)
51+ return
52+ }
49 if (cache) {53 if (cache) {
50 Log.debug(`try to use cache base64 file`)54 Log.debug(`try to use cache base64 file`)
51 this.useCache(fileType, '', fileName, callback, async () => {55 this.useCache(fileType, '', fileName, callback, async () => {
@@ -99,23 +103,27 @@ export class DocViewTurboModule extends TurboModule{
99 }103 }
100 }104 }
101 getFilePath(fileName: string, url?: string) {105 getFilePath(fileName: string, url?: string) {
102- const context = this.ctx.uiAbilityContext106+ let name = fileName ?? ''
103- let filedDir = this.tempDir107+ if (!name && url) {
104- if (fileName) {108+ name = url.split('/').pop() ?? ''
105- return `${filedDir}/${fileName}`
106 }109 }
107- if (url) {110+ name = name.split('?')[0].split('#')[0]
108- const urlSplit = url?.split('/')111+ name = name.split('/').pop() ?? ''
109- const name = urlSplit[urlSplit.length - 1]112+ name = name.split('\\').pop() ?? ''
110- Log.debug(`getFilePath name:${name}`)113+ if (!name || name === '.' || name === '..') {
111- const filePath = filedDir + `/${name}`114+ Log.debug(`getFilePath invalid fileName:${JSON.stringify(fileName)}, url:${url}`)
112- return filePath115+ return ''
113 }116 }
114- return ''117+ Log.debug(`getFilePath name:${name}`)
118+ return `${this.tempDir}/${name}`
115 }119 }
116 async useCache(fileType: string, url: string, fileName: string, callback: Function, notExistsFn?: Function) {120 async useCache(fileType: string, url: string, fileName: string, callback: Function, notExistsFn?: Function) {
117 Log.debug(`useCache start`)121 Log.debug(`useCache start`)
118 const filePath = this.getFilePath(fileName, url)122 const filePath = this.getFilePath(fileName, url)
123+ if (!filePath) {
124+ callback(`invalid fileName:${fileName}`)
125+ return
126+ }
119 const isExists = await fs.access(filePath)127 const isExists = await fs.access(filePath)
120 if (isExists) {128 if (isExists) {
121 Log.debug(`useCache isExists:${filePath}`)129 Log.debug(`useCache isExists:${filePath}`)
@@ -140,43 +148,61 @@ export class DocViewTurboModule extends TurboModule{
140 Log.debug(`removeFile err:${JSON.stringify(err)}`)148 Log.debug(`removeFile err:${JSON.stringify(err)}`)
141 }149 }
142 }150 }
151+ normalizeUrl(url: string): string {
152+ const idx = url.indexOf('://');
153+ if (idx > 0) {
154+ return `${url.substring(0, idx).toLowerCase()}://${url.substring(idx + 3)}`;
155+ }
156+ return url;
157+ }
143 async download(url: string, fileType: string, fileName: string, callback: Function) {158 async download(url: string, fileType: string, fileName: string, callback: Function) {
144- Log.debug(`download start url:${url}`)159+ url = this.normalizeUrl(url);
145- const filePath = this.getFilePath(fileName, url)160+ Log.debug(`download start url:${url}`);
146- this.removeFile(filePath)161+ const filePath = this.getFilePath(fileName, url);
147- const context = this.ctx.uiAbilityContext162+ if (!filePath) {
148- try{163+ callback(`invalid fileName:${fileName}`);
149- request.downloadFile(context, {164+ return;
165+ }
166+ await this.removeFile(filePath);
167+ const context = this.ctx.uiAbilityContext;
168+ try {
169+ const downloadTask = await request.downloadFile(context, {
150 url,170 url,
151 filePath171 filePath
152- }).then(downloadTask => {172+ });
153- Log.debug(`downloadTask start`)173+ Log.debug(`downloadTask start`);
154- downloadTask.on('complete', () => {174+ this.watchDownloadTask(downloadTask, filePath, fileType, fileName, callback);
155- Log.debug(`download complete:${fileName}`)
156- this.shareFile(filePath, fileType, callback)
157- })
158- downloadTask.on('fail', () => {
159- Log.debug(`download fail:${fileName}`)
160- this.removeFile(filePath)
161- callback(`download fail`)
162- })
163- }).catch(err => {
164- Log.debug(`Invoke catch downloadTask failed:${JSON.stringify(err)}`)
165- })
166 } catch (err) {175 } catch (err) {
167- Log.debug(`Invoke catch downloadTask failed:${JSON.stringify(err)}`)176+ Log.debug(`Invoke catch downloadTask failed:${JSON.stringify(err)}`);
168 if (err.code === 13400002) {177 if (err.code === 13400002) {
169- Log.debug(`file is exists:${fileName}`)178+ Log.debug(`file is exists:${fileName}`);
170- this.shareFile(filePath, fileType, callback)179+ this.shareFile(filePath, fileType, callback);
171 } else {180 } else {
172- callback(`download fail`)181+ callback(`download fail`);
173 }182 }
174 }183 }
175 }184 }
185+ 
186+ watchDownloadTask(downloadTask: request.DownloadTask, filePath: string, fileType: string, fileName: string, callback: Function) {
187+ downloadTask.on('complete', () => {
188+ Log.debug(`download complete:${fileName}`);
189+ this.shareFile(filePath, fileType, callback);
190+ });
191+ downloadTask.on('fail', (err: number) => {
192+ Log.debug(`download fail:${fileName}, code:${err}`);
193+ this.removeFile(filePath);
cpf-managercpf-manager
cpf-managercpf-manager9月4日

【AI-Review】【一般】【基础代码问题】【稳定性问题】fail 回调中 removeFile 未 await 导致残留文件被误共享

● 问题:watchDownloadTask 方法的 fail 回调(turborModules.ts:193)调用 this.removeFile(filePath) 未使用 await。removeFile 为 async 方法(turborModules.ts:140),内部通过 await fs.access(filePath) 判断文件存在后执行 await fs.unlink(filePath) 删除文件。未 await 时 callback 被立即调用(turborModules.ts:194),文件删除在后台异步执行,此时下载失败的残留文件尚未被删除。

● 影响:JS 侧收到下载失败回调后,若使用相同 fileName 且 cache=true 再次调用 openDoc/openDocBinaryinUrl(turborModules.ts:89-90),会进入 useCache(turborModules.ts:120)。useCache 执行 await fs.access(filePath)(turborModules.ts:127)检查文件是否存在,若 removeFile 的 fs.unlink 尚未完成(竞态条件),useCache 判定文件存在并调用 shareFile(turborModules.ts:130)直接共享该残留文件,导致用户看到损坏或不完整的文档。

● 建议:将 fail 回调改为 async 函数并 await removeFile: downloadTask.on('fail', async (err: number) => { Log.debug(download fail:${fileName}, code:${err}); await this.removeFile(filePath); callback(download fail, code:${err}); });

likedislike
cpf-managercpf-manager9月4日

【AI-Review】【严重】【基础代码问题】【稳定性问题】watchDownloadTask 的 fail 回调中 removeFile 未 await,callback 在文件清理完成前发出

● 问题:在 watchDownloadTask 方法(turborModules.ts:191-195)的 fail 回调中,第193行 this.removeFile(filePath); 未使用 await 修饰。removeFile 为 async 方法(turborModules.ts:140-150),内部执行了 await fs.access(filePath) 与 await fs.unlink(filePath)。由于未 await,紧接的第194行 callback(download fail, code:${err}) 会立即执行,此时 removeFile 的文件删除尚未完成。

● 影响:严重。当 cache=true 场景下下载失败后用户重试时,重试流程会进入 useCache(turborModules.ts:120-139)的 await fs.access(filePath) 分支。若重试发生在 fail 回调的 removeFile 完成之前,fs.access 会检测到残留的半成品文件并返回 true,useCache 随即调用 this.shareFile(filePath, fileType, callback)(turborModules.ts:130)直接分享该损坏的半成品文件,用户看到的是无法正常打开的损坏文档。该问题与本 PR 已修复的 download 方法第166行 removeFile 未 await(已通过评论提交并修复)属同一模式,但作者在新抽取的 watchDownloadTask 方法中遗漏了 await 修饰。

● 建议:将 fail 回调改为 async 并 await removeFile,确保文件清理完成后再通知 JS 侧: downloadTask.on('fail', async (err: number) => { Log.debug(download fail:${fileName}, code:${err}); await this.removeFile(filePath); callback(download fail, code:${err}); });

likedislike
194+ callback(`download fail, code:${err}`);
195+ });
196+ }
176 shareFile(filePath: string, fileType: string, callback: Function) {197 shareFile(filePath: string, fileType: string, callback: Function) {
177- const uri = fileUri.getUriFromPath(filePath)198+ try {
178- Log.debug(`shareFile uri: ${uri}, filePath: ${filePath}`)199+ const uri = fileUri.getUriFromPath(filePath)
179- this.start(uri, fileType, callback)200+ Log.debug(`shareFile uri: ${uri}, filePath: ${filePath}`)
201+ this.start(uri, fileType, callback)
202+ } catch (e) {
203+ Log.debug(`shareFile err:${JSON.stringify(e)}`)
204+ callback(`shareFile err:${JSON.stringify(e)}`)
205+ }
180 }206 }
181 start(uri: string, fileType: string, callback: Function) {207 start(uri: string, fileType: string, callback: Function) {
182 const mimeType = getMimeType(fileType)208 const mimeType = getMimeType(fileType)
Binary files do not support preview
@@ -1,6 +1,6 @@
1{1{
2 "name": "@react-native-ohos/react-native-doc-viewer",2 "name": "@react-native-ohos/react-native-doc-viewer",
3- "version": "2.8.0",3+ "version": "2.8.1-beta.1",
4 "description": "React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported ",4 "description": "React Native Native Module Bridge Quicklock Document Viewer for IOS + Android supports pdf, png, jpg, xls, ppt, doc, docx, pptx, xlx + Video Player mp4 supported ",
5 "main": "lib/commonjs/index",5 "main": "lib/commonjs/index",
6 "scripts": {6 "scripts": {