├── entry ├── src │ ├── mock │ │ └── mock-config.json5 │ ├── main │ │ ├── resources │ │ │ ├── base │ │ │ │ ├── profile │ │ │ │ │ ├── backup_config.json │ │ │ │ │ └── main_pages.json │ │ │ │ ├── media │ │ │ │ │ ├── background.png │ │ │ │ │ ├── foreground.png │ │ │ │ │ ├── startIcon.png │ │ │ │ │ └── layered_image.json │ │ │ │ └── element │ │ │ │ │ ├── color.json │ │ │ │ │ └── string.json │ │ │ ├── zh_CN │ │ │ │ └── element │ │ │ │ │ └── string.json │ │ │ ├── en_US │ │ │ │ └── element │ │ │ │ │ └── string.json │ │ │ └── rawfile │ │ │ │ └── index.html │ │ ├── ets │ │ │ ├── entrybackupability │ │ │ │ └── EntryBackupAbility.ets │ │ │ ├── entryability │ │ │ │ └── EntryAbility.ets │ │ │ └── pages │ │ │ │ └── Index.ets │ │ └── module.json5 │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ └── ohosTest │ │ ├── ets │ │ └── test │ │ │ ├── List.test.ets │ │ │ └── Ability.test.ets │ │ └── module.json5 ├── .gitignore ├── hvigorfile.ts ├── oh-package.json5 ├── build-profile.json5 ├── oh-package-lock.json5 └── obfuscation-rules.txt ├── webview_javascript_bridge ├── consumer-rules.txt ├── .gitignore ├── CHANGELOG.md ├── src │ ├── test │ │ ├── List.test.ets │ │ └── LocalUnit.test.ets │ ├── ohosTest │ │ ├── ets │ │ │ └── test │ │ │ │ ├── List.test.ets │ │ │ │ └── Ability.test.ets │ │ └── module.json5 │ └── main │ │ ├── resources │ │ ├── base │ │ │ └── element │ │ │ │ └── string.json │ │ ├── en_US │ │ │ └── element │ │ │ │ └── string.json │ │ └── zh_CN │ │ │ └── element │ │ │ └── string.json │ │ ├── module.json5 │ │ └── ets │ │ ├── WebViewJavascriptBridge.ets │ │ ├── WebViewJavascriptBridge_JS.ets │ │ ├── WebViewJavascriptBridgeBase.ets │ │ └── WebViewJavascriptBridgeTools.ets ├── hvigorfile.ts ├── Index.ets ├── oh-package.json5 ├── BuildProfile.ets ├── build-profile.json5 ├── obfuscation-rules.txt ├── LICENSE └── README.md ├── wvjb_demo.gif ├── AppScope ├── resources │ └── base │ │ ├── media │ │ └── app_icon.png │ │ └── element │ │ └── string.json └── app.json5 ├── .gitignore ├── oh-package.json5 ├── hvigorfile.ts ├── oh-package-lock.json5 ├── hvigor └── hvigor-config.json5 └── README.md /entry/src/mock/mock-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/consumer-rules.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/backup_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowToBackupRestore": true 3 | } -------------------------------------------------------------------------------- /wvjb_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ilI/WebViewJavascriptBridge_harmony/HEAD/wvjb_demo.gif -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /webview_javascript_bridge/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /webview_javascript_bridge/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 版本更新记录 3 | 4 | ### 2024/07/01 - 1.0.1 5 | 修改最低支持版本为 API 10 6 | 7 | ### 2024/06/28 - 1.0.0 8 | 初始发布 -------------------------------------------------------------------------------- /entry/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ilI/WebViewJavascriptBridge_harmony/HEAD/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ilI/WebViewJavascriptBridge_harmony/HEAD/entry/src/main/resources/base/media/background.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ilI/WebViewJavascriptBridge_harmony/HEAD/entry/src/main/resources/base/media/foreground.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/startIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1ilI/WebViewJavascriptBridge_harmony/HEAD/entry/src/main/resources/base/media/startIcon.png -------------------------------------------------------------------------------- /webview_javascript_bridge/src/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import localUnitTest from './LocalUnit.test'; 2 | 3 | export default function testsuite() { 4 | localUnitTest(); 5 | } -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "WebViewJavascriptBridge" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test'; 2 | 3 | export default function testsuite() { 4 | abilityTest(); 5 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/layered_image.json: -------------------------------------------------------------------------------- 1 | { 2 | "layered-image": 3 | { 4 | "background" : "$media:background", 5 | "foreground" : "$media:foreground" 6 | } 7 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/resources/en_US/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /local.properties 4 | /.idea 5 | **/build 6 | /.hvigor 7 | .cxx 8 | /.clangd 9 | /.clang-format 10 | /.clang-tidy 11 | **/.test 12 | /.appanalyzer 13 | **/.DS_Store -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "description": "Please describe the basic information.", 4 | "dependencies": { 5 | }, 6 | "devDependencies": { 7 | "@ohos/hypium": "1.0.18", 8 | "@ohos/hamock": "1.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { appTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { hapTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "webview_javascript_bridge", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.yue.WebViewJavascriptBridge", 4 | "vendor": "example", 5 | "versionCode": 1000000, 6 | "versionName": "1.0.0", 7 | "icon": "$media:app_icon", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webview_javascript_bridge/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | import { harTasks } from '@ohos/hvigor-ohos-plugin'; 2 | 3 | export default { 4 | system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ 5 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ 6 | } 7 | -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "phone", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ], 11 | "deliveryWithInstall": true, 12 | "installationFree": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "entry", 3 | "version": "1.0.0", 4 | "description": "Please describe the basic information.", 5 | "main": "", 6 | "author": "", 7 | "license": "", 8 | "dependencies": { 9 | '@yue/WebViewJavascriptBridge' : 'file:../webview_javascript_bridge' 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /entry/src/main/resources/zh_CN/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_desc", 5 | "value": "模块描述" 6 | }, 7 | { 8 | "name": "EntryAbility_desc", 9 | "value": "description" 10 | }, 11 | { 12 | "name": "EntryAbility_label", 13 | "value": "label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "webview_javascript_bridge_test", 4 | "type": "feature", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet", 8 | "2in1", 9 | "car" 10 | ], 11 | "deliveryWithInstall": true, 12 | "installationFree": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 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": "label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/main/resources/en_US/element/string.json: -------------------------------------------------------------------------------- 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": "label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/Index.ets: -------------------------------------------------------------------------------- 1 | 2 | 3 | export { WVJBDelegate } from './src/main/ets/WebViewJavascriptBridgeBase' 4 | 5 | export { WVJBResponseCallback } from './src/main/ets/WebViewJavascriptBridgeBase' 6 | 7 | export { WebViewJavascriptBridge } from './src/main/ets/WebViewJavascriptBridge' 8 | 9 | export { WebViewJavascriptBridgeTools } from './src/main/ets/WebViewJavascriptBridgeTools' -------------------------------------------------------------------------------- /entry/src/main/ets/entrybackupability/EntryBackupAbility.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; 3 | 4 | export default class EntryBackupAbility extends BackupExtensionAbility { 5 | async onBackup() { 6 | hilog.info(0x0000, 'testTag', 'onBackup ok'); 7 | } 8 | 9 | async onRestore(bundleVersion: BundleVersion) { 10 | hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); 11 | } 12 | } -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": true, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | } 16 | } 17 | } 18 | }, 19 | ], 20 | "targets": [ 21 | { 22 | "name": "default" 23 | }, 24 | { 25 | "name": "ohosTest", 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yue/webview_javascript_bridge", 3 | "version": "1.0.1", 4 | "description": "WebViewJavascriptBridge 在 HarmonyOS (API 10+)上的实现", 5 | "repository": "https://github.com/1ilI/WebViewJavascriptBridge_harmony.git", 6 | "homepage": "https://github.com/1ilI/WebViewJavascriptBridge_harmony", 7 | "main": "Index.ets", 8 | "author": "yue", 9 | "license": "Apache-2.0", 10 | "keywords": [ 11 | "JSBridge", 12 | "WebView", 13 | "bridge loaded", 14 | "wvjb queue message", 15 | "桥接交互", 16 | ], 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /webview_javascript_bridge/BuildProfile.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * Use these variables when you tailor your ArkTS code. They must be of the const type. 3 | */ 4 | export const HAR_VERSION = '1.0.1'; 5 | export const BUILD_MODE_NAME = 'debug'; 6 | export const DEBUG = true; 7 | export const TARGET_NAME = 'default'; 8 | 9 | /** 10 | * BuildProfile Class is used only for compatibility purposes. 11 | */ 12 | export default class BuildProfile { 13 | static readonly HAR_VERSION = HAR_VERSION; 14 | static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; 15 | static readonly DEBUG = DEBUG; 16 | static readonly TARGET_NAME = TARGET_NAME; 17 | } -------------------------------------------------------------------------------- /entry/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@yue/WebViewJavascriptBridge@../webview_javascript_bridge": "@yue/WebViewJavascriptBridge@../webview_javascript_bridge" 9 | }, 10 | "packages": { 11 | "@yue/WebViewJavascriptBridge@../webview_javascript_bridge": { 12 | "name": "@yue/webview_javascript_bridge", 13 | "version": "1.0.1", 14 | "resolved": "../webview_javascript_bridge", 15 | "registryType": "local" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | }, 5 | "buildOptionSet": [ 6 | { 7 | "name": "release", 8 | "arkOptions": { 9 | "obfuscation": { 10 | "ruleOptions": { 11 | "enable": true, 12 | "files": [ 13 | "./obfuscation-rules.txt" 14 | ] 15 | }, 16 | "consumerFiles": [ 17 | "./consumer-rules.txt" 18 | ] 19 | } 20 | }, 21 | }, 22 | ], 23 | "targets": [ 24 | { 25 | "name": "default" 26 | }, 27 | { 28 | "name": "ohosTest" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /entry/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope -------------------------------------------------------------------------------- /webview_javascript_bridge/obfuscation-rules.txt: -------------------------------------------------------------------------------- 1 | # Define project specific obfuscation rules here. 2 | # You can include the obfuscation configuration files in the current module's build-profile.json5. 3 | # 4 | # For more details, see 5 | # https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 6 | 7 | # Obfuscation options: 8 | # -disable-obfuscation: disable all obfuscations 9 | # -enable-property-obfuscation: obfuscate the property names 10 | # -enable-toplevel-obfuscation: obfuscate the names in the global scope 11 | # -compact: remove unnecessary blank spaces and all line feeds 12 | # -remove-log: remove all console.* statements 13 | # -print-namecache: print the name cache that contains the mapping from the old names to new names 14 | # -apply-namecache: reuse the given cache file 15 | 16 | # Keep options: 17 | # -keep-property-name: specifies property names that you want to keep 18 | # -keep-global-name: specifies names that you want to keep in the global scope -------------------------------------------------------------------------------- /oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": true 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", 9 | "@ohos/hypium@1.0.18": "@ohos/hypium@1.0.18" 10 | }, 11 | "packages": { 12 | "@ohos/hamock@1.0.0": { 13 | "name": "@ohos/hamock", 14 | "version": "1.0.0", 15 | "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", 16 | "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har", 17 | "registryType": "ohpm" 18 | }, 19 | "@ohos/hypium@1.0.18": { 20 | "name": "@ohos/hypium", 21 | "version": "1.0.18", 22 | "integrity": "sha512-RGe/iLGdeywdQilMWZsHKUoiE9OJ+9QxQsorF92R2ImLNVHVhbpSePNITGpW7TnvLgOIP/jscOqfIOhk6X7XRQ==", 23 | "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.18.har", 24 | "registryType": "ohpm" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "dependencies": { 4 | }, 5 | "execution": { 6 | // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ 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": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ 20 | // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /entry/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry", 4 | "type": "entry", 5 | "description": "$string:module_desc", 6 | "mainElement": "EntryAbility", 7 | "deviceTypes": [ 8 | "phone", 9 | "tablet", 10 | "2in1", 11 | "car" 12 | ], 13 | "deliveryWithInstall": true, 14 | "installationFree": false, 15 | "pages": "$profile:main_pages", 16 | "abilities": [ 17 | { 18 | "name": "EntryAbility", 19 | "srcEntry": "./ets/entryability/EntryAbility.ets", 20 | "description": "$string:EntryAbility_desc", 21 | "icon": "$media:layered_image", 22 | "label": "$string:EntryAbility_label", 23 | "startWindowIcon": "$media:startIcon", 24 | "startWindowBackground": "$color:start_window_background", 25 | "exported": true, 26 | "skills": [ 27 | { 28 | "entities": [ 29 | "entity.system.home" 30 | ], 31 | "actions": [ 32 | "action.system.home" 33 | ] 34 | } 35 | ] 36 | } 37 | ], 38 | "extensionAbilities": [ 39 | { 40 | "name": "EntryBackupAbility", 41 | "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", 42 | "type": "backup", 43 | "exported": false, 44 | "metadata": [ 45 | { 46 | "name": "ohos.extension.backup", 47 | "resource": "$profile:backup_config" 48 | } 49 | ], 50 | } 51 | ] 52 | } 53 | } -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 2 | import { hilog } from '@kit.PerformanceAnalysisKit'; 3 | import { window } from '@kit.ArkUI'; 4 | 5 | export default class EntryAbility extends UIAbility { 6 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 7 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 8 | } 9 | 10 | onDestroy(): void { 11 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 12 | } 13 | 14 | onWindowStageCreate(windowStage: window.WindowStage): void { 15 | // Main window is created, set main page for this ability 16 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 17 | 18 | windowStage.loadContent('pages/Index', (err) => { 19 | if (err.code) { 20 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 21 | return; 22 | } 23 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.'); 24 | }); 25 | } 26 | 27 | onWindowStageDestroy(): void { 28 | // Main window is destroyed, release UI related resources 29 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 30 | } 31 | 32 | onForeground(): void { 33 | // Ability has brought to foreground 34 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 35 | } 36 | 37 | onBackground(): void { 38 | // Ability has back to background 39 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /entry/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/test/LocalUnit.test.ets: -------------------------------------------------------------------------------- 1 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 2 | 3 | export default function localUnitTest() { 4 | describe('localUnitTest', () => { 5 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 6 | beforeAll(() => { 7 | // Presets an action, which is performed only once before all test cases of the test suite start. 8 | // This API supports only one parameter: preset action function. 9 | }); 10 | beforeEach(() => { 11 | // Presets an action, which is performed before each unit test case starts. 12 | // The number of execution times is the same as the number of test cases defined by **it**. 13 | // This API supports only one parameter: preset action function. 14 | }); 15 | afterEach(() => { 16 | // Presets a clear action, which is performed after each unit test case ends. 17 | // The number of execution times is the same as the number of test cases defined by **it**. 18 | // This API supports only one parameter: clear action function. 19 | }); 20 | afterAll(() => { 21 | // Presets a clear action, which is performed after all test cases of the test suite end. 22 | // This API supports only one parameter: clear action function. 23 | }); 24 | it('assertContain', 0, () => { 25 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 26 | let a = 'abc'; 27 | let b = 'b'; 28 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 29 | expect(a).assertContain(b); 30 | expect(a).assertEqual(a); 31 | }); 32 | }); 33 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import { hilog } from '@kit.PerformanceAnalysisKit'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', () => { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(() => { 8 | // Presets an action, which is performed only once before all test cases of the test suite start. 9 | // This API supports only one parameter: preset action function. 10 | }) 11 | beforeEach(() => { 12 | // Presets an action, which is performed before each unit test case starts. 13 | // The number of execution times is the same as the number of test cases defined by **it**. 14 | // This API supports only one parameter: preset action function. 15 | }) 16 | afterEach(() => { 17 | // Presets a clear action, which is performed after each unit test case ends. 18 | // The number of execution times is the same as the number of test cases defined by **it**. 19 | // This API supports only one parameter: clear action function. 20 | }) 21 | afterAll(() => { 22 | // Presets a clear action, which is performed after all test cases of the test suite end. 23 | // This API supports only one parameter: clear action function. 24 | }) 25 | it('assertContain', 0, () => { 26 | // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. 27 | hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); 28 | let a = 'abc'; 29 | let b = 'b'; 30 | // Defines a variety of assertion methods, which are used to declare expected boolean conditions. 31 | expect(a).assertContain(b); 32 | expect(a).assertEqual(a); 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | WebViewJavascriptBridge 11 | 12 | 13 | 14 | 82 | 83 |

WebViewJavascriptBridge

84 | 85 |
86 | 87 | 88 |
89 | 90 |
91 |
92 |
93 |
94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/ets/WebViewJavascriptBridge.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yue 2024. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * Created by Yue on 2024/6/24. 16 | */ 17 | 18 | import { 19 | WebViewJavascriptBridgeBase, 20 | WVJBDelegate, 21 | WVJBHandler, 22 | WVJBResponseCallback 23 | } from './WebViewJavascriptBridgeBase'; 24 | import { FetchQueueJS } from './WebViewJavascriptBridge_JS'; 25 | 26 | export interface SendParams { 27 | data: Object, 28 | responseCallback: WVJBResponseCallback, 29 | } 30 | 31 | export interface CallParams { 32 | data?: Object, 33 | handlerName: string, 34 | responseCallback?: WVJBResponseCallback, 35 | } 36 | 37 | export class WebViewJavascriptBridge implements WVJBDelegate { 38 | private _base: WebViewJavascriptBridgeBase = new WebViewJavascriptBridgeBase(); 39 | private _webController: WebviewController | undefined; 40 | 41 | constructor(forWebController: WebviewController) { 42 | this._base.delegate = this; 43 | this._webController = forWebController; 44 | } 45 | 46 | public static enableLogging() { 47 | WebViewJavascriptBridgeBase.enableLogging(); 48 | } 49 | 50 | public bridgeInterceptWithURL(url: string): boolean { 51 | if (this._base.isBridgeLoadedURL(url)) { 52 | this._base.injectJavascriptFile(); 53 | return true; 54 | } else if (this._base.isQueueMessageURL(url)) { 55 | this._flushMessageQueue(); 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | public registerHandler(handlerName: string, handler: WVJBHandler) { 62 | this._base.messageHandlers.set(handlerName, handler); 63 | } 64 | 65 | public removeHandler(handlerName: string) { 66 | if (this._base.messageHandlers.has(handlerName)) { 67 | this._base.messageHandlers.delete(handlerName); 68 | } 69 | } 70 | 71 | public send(params: SendParams) { 72 | this._base.sendData(params?.data, params?.responseCallback, undefined); 73 | } 74 | 75 | public callHandler(params: CallParams) { 76 | this._base.sendData(params?.data, params?.responseCallback, params?.handlerName); 77 | } 78 | 79 | public reset() { 80 | this._base.reset(); 81 | } 82 | 83 | evaluateJavascript(javascriptCommand: string): void { 84 | if (this._webController) { 85 | this._webController.runJavaScriptExt(javascriptCommand, (error, result) => { 86 | }); 87 | } else { 88 | console.log('❌ WebViewJavascriptBridge 未初始化'); 89 | } 90 | } 91 | 92 | private _flushMessageQueue() { 93 | if (this._webController) { 94 | this._webController.runJavaScriptExt(FetchQueueJS, (error, result) => { 95 | if (error) { 96 | console.log('❌执行 js 方法 _fetchQueue 出错' + error.message); 97 | } 98 | this._base.flushMessageQueue(result.getString()); 99 | }) 100 | } else { 101 | console.log('❌ WebViewJavascriptBridge 未初始化'); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/ets/WebViewJavascriptBridge_JS.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yue 2024. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * Created by Yue on 2024/6/11. 16 | */ 17 | 18 | export const WebViewJavascriptBridge_JS = ` 19 | ;(function() { 20 | if (window.WebViewJavascriptBridge) { 21 | return; 22 | } 23 | 24 | if (!window.onerror) { 25 | window.onerror = function(msg, url, line) { 26 | console.log("WebViewJavascriptBridge: ERROR:" + msg + "@" + url + ":" + line); 27 | } 28 | } 29 | window.WebViewJavascriptBridge = { 30 | registerHandler: registerHandler, 31 | callHandler: callHandler, 32 | disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout, 33 | _fetchQueue: _fetchQueue, 34 | _handleMessageFromHarmony: _handleMessageFromHarmony 35 | }; 36 | 37 | var messagingIframe; 38 | var sendMessageQueue = []; 39 | var messageHandlers = {}; 40 | 41 | var CUSTOM_PROTOCOL_SCHEME = 'https'; 42 | var QUEUE_HAS_MESSAGE = '__wvjb_queue_message__'; 43 | 44 | var responseCallbacks = {}; 45 | var uniqueId = 1; 46 | var dispatchMessagesWithTimeoutSafety = true; 47 | 48 | function registerHandler(handlerName, handler) { 49 | messageHandlers[handlerName] = handler; 50 | } 51 | 52 | function callHandler(handlerName, data, responseCallback) { 53 | if (arguments.length == 2 && typeof data == 'function') { 54 | responseCallback = data; 55 | data = null; 56 | } 57 | _doSend({ handlerName:handlerName, data:data }, responseCallback); 58 | } 59 | function disableJavscriptAlertBoxSafetyTimeout() { 60 | dispatchMessagesWithTimeoutSafety = false; 61 | } 62 | 63 | function _doSend(message, responseCallback) { 64 | if (responseCallback) { 65 | var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime(); 66 | responseCallbacks[callbackId] = responseCallback; 67 | message['callbackId'] = callbackId; 68 | } 69 | sendMessageQueue.push(message); 70 | messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 71 | } 72 | 73 | function _fetchQueue() { 74 | var messageQueueString = JSON.stringify(sendMessageQueue); 75 | sendMessageQueue = []; 76 | return messageQueueString; 77 | } 78 | 79 | function _dispatchMessageFromHarmony(messageJSON) { 80 | if (dispatchMessagesWithTimeoutSafety) { 81 | setTimeout(_doDispatchMessageFromHarmony); 82 | } else { 83 | _doDispatchMessageFromHarmony(); 84 | } 85 | 86 | function _doDispatchMessageFromHarmony() { 87 | var message = JSON.parse(messageJSON); 88 | var messageHandler; 89 | var responseCallback; 90 | 91 | if (message.responseId) { 92 | responseCallback = responseCallbacks[message.responseId]; 93 | if (!responseCallback) { 94 | return; 95 | } 96 | responseCallback(message.responseData); 97 | delete responseCallbacks[message.responseId]; 98 | } else { 99 | if (message.callbackId) { 100 | var callbackResponseId = message.callbackId; 101 | responseCallback = function(responseData) { 102 | _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData }); 103 | }; 104 | } 105 | 106 | var handler = messageHandlers[message.handlerName]; 107 | if (!handler) { 108 | console.log("WebViewJavascriptBridge: WARNING: no handler for message from Harmony:", JSON.stringify(message)); 109 | } else { 110 | handler(message.data, responseCallback); 111 | } 112 | } 113 | } 114 | } 115 | 116 | function _handleMessageFromHarmony(messageJSON) { 117 | _dispatchMessageFromHarmony(messageJSON); 118 | } 119 | 120 | messagingIframe = document.createElement('iframe'); 121 | messagingIframe.style.display = 'none'; 122 | messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE; 123 | document.documentElement.appendChild(messagingIframe); 124 | 125 | registerHandler("_disableJavascriptAlertBoxSafetyTimeout", disableJavscriptAlertBoxSafetyTimeout); 126 | 127 | setTimeout(_callWVJBCallbacks, 0); 128 | function _callWVJBCallbacks() { 129 | var callbacks = window.WVJBCallbacks; 130 | if (callbacks) { 131 | delete window.WVJBCallbacks; 132 | for (var i=0; i { 41 | this.selectHasArgs = !this.selectHasArgs; 42 | }) 43 | Checkbox({ name: '包含参数' }).select(this.selectHasArgs).onChange((value) => { 44 | this.selectHasArgs = value; 45 | }) 46 | }.margin({ top: 10, bottom: 5 }) 47 | 48 | Button('h5注册方法,原生从 h5 获取信息').onClick(() => { 49 | this._getH5Info(); 50 | }) 51 | } 52 | .width('100%') 53 | .height('30%') 54 | .backgroundColor(Color.Gray) 55 | } 56 | 57 | @Builder 58 | private webView() { 59 | Web({ 60 | src: $rawfile('index.html'), 61 | controller: this._controller, 62 | }) 63 | .width('100%') 64 | .height('70%') 65 | .zoomAccess(false)// 不允许缩放 66 | .domStorageAccess(true) 67 | .onControllerAttached(() => { 68 | // WebviewController 载入完成后,设置 WebViewJavascriptBridge 69 | this.setupWebViewJavascriptBridge(); 70 | }) 71 | .onAlert((event) => { 72 | AlertDialog.show({ message: event?.message }) 73 | return false; 74 | }) 75 | .onLoadIntercept((event) => { 76 | const url = event.data.getRequestUrl(); 77 | // 符合 WebViewJavascriptBridge 的链接规则才拦截 78 | if (this._bridge?.bridgeInterceptWithURL(url)) { 79 | return true; 80 | } 81 | return false; 82 | }); 83 | } 84 | 85 | /** 86 | * 设置 jsBridge 87 | */ 88 | private setupWebViewJavascriptBridge() { 89 | // 开启调试,控制台打印信息,默认无调试 90 | WebViewJavascriptBridge.enableLogging(); 91 | // 创建 WebViewJavascriptBridge 92 | this._bridge = new WebViewJavascriptBridge(this._controller); 93 | // 创建后,注册相关方法 94 | this._bridge.registerHandler('getNativeInfo', (data: Object, responseCallback: WVJBResponseCallback) => { 95 | if (data) { 96 | this._toast("JS 调用 getNativeInfo \n原生接收到参数:" + WebViewJavascriptBridgeTools.jsonStringify(data)); 97 | } else { 98 | this._toast("JS 调用 getNativeInfo 没有向原生传递参数"); 99 | } 100 | 101 | let map1 = new Map>([ 102 | ["deviceInfo", new Map([ 103 | ["types", "harmony"], 104 | ["debug", "1"], 105 | ])], 106 | ["userInfo", new Map([ 107 | ["userId", "666666"], 108 | ["member_name", "用户名"], 109 | ])], 110 | ]); 111 | let map2: Record> = { 112 | "deviceInfo": { 113 | "types": "harmony", 114 | "debug": "1", 115 | }, 116 | "userInfo": { 117 | "userId": "666666", 118 | "member_name": "用户名", 119 | } 120 | } 121 | // map1 和 map2 均可嵌套,生成 json 字符串,返回给 responseCallback 122 | const jsonStr: string = WebViewJavascriptBridgeTools.jsonStringify(map1); 123 | // const jsonStr: string = WebViewJavascriptBridgeTools.jsonStringify(map2); 124 | responseCallback(jsonStr); 125 | }); 126 | 127 | 128 | // 原生主动调用 H5 方法,获取 H5 侧的信息 129 | this._bridge.callHandler({ 130 | handlerName: 'getH5InfoOnStar', responseCallback: (responseData) => { 131 | this._toast("初始化时原生调用 getH5InfoOnStar \n接收到结果:" + 132 | WebViewJavascriptBridgeTools.jsonStringify(responseData)); 133 | } 134 | }) 135 | 136 | } 137 | 138 | private _getH5Info() { 139 | let arg1 = new Map>([ 140 | ["key", "1"], 141 | ["h5Info", new Map([ 142 | ["id", "1111"], 143 | ["k", "v"], 144 | ])], 145 | ]); 146 | let arg2: Record> = { 147 | "key": "2", 148 | "h5Info": { "id": "222", "k2": "v2" } 149 | } 150 | 151 | if (this.selectHasArgs) { 152 | this._bridge?.callHandler({ 153 | handlerName: 'getH5Info', 154 | data: arg2, 155 | responseCallback: (responseData) => { 156 | this._toast(WebViewJavascriptBridgeTools.jsonStringify(responseData)); 157 | } 158 | }) 159 | } 160 | // 无参数传递 161 | else { 162 | this._bridge?.callHandler({ 163 | handlerName: 'getH5Info', 164 | responseCallback: (responseData) => { 165 | this._toast(WebViewJavascriptBridgeTools.jsonStringify(responseData)); 166 | } 167 | }) 168 | } 169 | } 170 | 171 | private _toast(msg: string) { 172 | promptAction.showToast({ 173 | message: msg, 174 | duration: 4000 175 | }); 176 | } 177 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/ets/WebViewJavascriptBridgeBase.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yue 2024. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * Created by Yue on 2024/6/24. 16 | */ 17 | 18 | import { WebViewJavascriptBridgeTools } from './WebViewJavascriptBridgeTools'; 19 | import { JSON } from '@kit.ArkTS'; 20 | import { WebViewJavascriptBridge_JS } from './WebViewJavascriptBridge_JS'; 21 | 22 | const _LogTAG: string = "[WebViewJavascriptBridge]"; 23 | let _logging: boolean = false; 24 | let _logMaxLength: number = 800; 25 | 26 | export type WVJBResponseCallback = (responseData: Object) => void; 27 | 28 | export type WVJBHandler = (data: Object, responseCallback: WVJBResponseCallback) => void; 29 | 30 | export interface WVJBDelegate { 31 | evaluateJavascript(javascriptCommand: string): void; 32 | } 33 | 34 | export class WebViewJavascriptBridgeBase { 35 | delegate?: WVJBDelegate; 36 | startupMessageQueue: Array> | undefined; 37 | responseCallbacks: Map = new Map(); 38 | messageHandlers: Map = new Map(); 39 | messageHandler?: WVJBHandler; 40 | private _uniqueId: number = 0; 41 | 42 | constructor() { 43 | this.startupMessageQueue = []; 44 | } 45 | 46 | public static enableLogging() { 47 | _logging = true; 48 | } 49 | 50 | public static setLogMaxLength(length: number) { 51 | _logMaxLength = length; 52 | } 53 | 54 | public reset() { 55 | this.startupMessageQueue = []; 56 | this.responseCallbacks.clear(); 57 | this._uniqueId = 0; 58 | } 59 | 60 | public sendData(data: Object | undefined, responseCallback: WVJBResponseCallback | undefined, 61 | handlerName: string | undefined): void { 62 | let message: Map = new Map(); 63 | if (!WebViewJavascriptBridgeTools.isEmpty(data)) { 64 | message.set("data", data!); 65 | } 66 | if (responseCallback) { 67 | this._uniqueId++; 68 | let callbackId: string = `harmony_cb_${this._uniqueId}`; 69 | this.responseCallbacks.set(callbackId, responseCallback); 70 | message.set("callbackId", callbackId); 71 | } 72 | // handlerName 73 | if (!WebViewJavascriptBridgeTools.isEmpty(handlerName)) { 74 | message.set("handlerName", handlerName!); 75 | } 76 | this._queueMessage(message); 77 | } 78 | 79 | public flushMessageQueue(messageQueueString: string) { 80 | if (messageQueueString.length == 0) { 81 | console.log(_LogTAG, '⚠️ 获取队列消息为空',); 82 | return; 83 | } 84 | const messages = WebViewJavascriptBridgeTools.jsonToObjArr(messageQueueString); 85 | for (const message of messages) { 86 | const messageMap = WebViewJavascriptBridgeTools.objToStrKeyMap(message); 87 | if (!messageMap.size) { 88 | console.log(_LogTAG, '⚠️ 解析队列消息异常:' + JSON.stringify(message)); 89 | continue; 90 | } 91 | this._log("接收", messageMap); 92 | 93 | // 若有 responseId ,则调用 responseCallback 94 | const responseId: string = WebViewJavascriptBridgeTools.getMapStringValue(messageMap, "responseId"); 95 | if (responseId.length) { 96 | const responseCallback: WVJBResponseCallback | undefined = this.responseCallbacks.get(responseId); 97 | if (responseCallback) { 98 | responseCallback(messageMap.get('responseData') ?? ''); 99 | } 100 | this.responseCallbacks.delete(responseId); 101 | } 102 | // 无 responseId,则看 callbackId 103 | else { 104 | let responseCallback: WVJBResponseCallback | undefined; 105 | // 若有 callbackId ,则处理回调 106 | const callbackId: string = WebViewJavascriptBridgeTools.getMapStringValue(messageMap, "callbackId"); 107 | if (callbackId.length) { 108 | responseCallback = (responseData: Object) => { 109 | const msg: Map = new Map([ 110 | ["responseId", callbackId], 111 | ["responseData", responseData], 112 | ]); 113 | this._queueMessage(msg); 114 | } 115 | } 116 | // 无,则回调不做任何处理 117 | else { 118 | responseCallback = (responseData: Object) => { 119 | } 120 | } 121 | 122 | // 返回 handler 123 | const handlerName: string = WebViewJavascriptBridgeTools.getMapStringValue(messageMap, "handlerName"); 124 | const handler: WVJBHandler | undefined = this.messageHandlers.get(handlerName); 125 | if (!handler) { 126 | console.log(_LogTAG, '⚠️ WVJBHandler 异常,无相关消息:' + JSON.stringify(message)); 127 | continue; 128 | } 129 | handler(messageMap.get('data') ?? '', responseCallback); 130 | } 131 | } 132 | } 133 | 134 | public injectJavascriptFile() { 135 | this._evaluateJavascript(WebViewJavascriptBridge_JS); 136 | if (this.startupMessageQueue) { 137 | let tmpStartupMessageQueue: Array> = []; 138 | this.startupMessageQueue.forEach((value) => { 139 | tmpStartupMessageQueue.push(value); 140 | }) 141 | this.startupMessageQueue = undefined; 142 | for (const queuedMessage of tmpStartupMessageQueue) { 143 | this._dispatchMessage(queuedMessage); 144 | } 145 | } 146 | } 147 | 148 | public isQueueMessageURL(url: string) { 149 | return this._isHttpScheme(url) && url.includes("__wvjb_queue_message__"); 150 | } 151 | 152 | public isBridgeLoadedURL(url: string) { 153 | return this._isHttpScheme(url) && url.includes("__bridge_loaded__"); 154 | } 155 | 156 | private _isHttpScheme(url: string) { 157 | return (url.startsWith("http://") || url.startsWith("https://")); 158 | } 159 | 160 | private _queueMessage(message: Map) { 161 | if (this.startupMessageQueue) { 162 | this.startupMessageQueue.push(message); 163 | } else { 164 | this._dispatchMessage(message); 165 | } 166 | } 167 | 168 | private _dispatchMessage(message: Map) { 169 | let msgJsonStr = WebViewJavascriptBridgeTools.jsonStringify(message); 170 | this._log("发送", message); 171 | msgJsonStr = msgJsonStr.replaceAll("\\", "\\\\"); 172 | msgJsonStr = msgJsonStr.replaceAll("\"", "\\\""); 173 | msgJsonStr = msgJsonStr.replaceAll("\'", "\\\'"); 174 | msgJsonStr = msgJsonStr.replaceAll("\n", "\\n"); 175 | msgJsonStr = msgJsonStr.replaceAll("\r", "\\r"); 176 | msgJsonStr = msgJsonStr.replaceAll("\f", "\\f"); 177 | msgJsonStr = msgJsonStr.replaceAll("\u2028", "\\u2028"); 178 | msgJsonStr = msgJsonStr.replaceAll("\u2029", "\\u2029"); 179 | let JSCommand = `WebViewJavascriptBridge._handleMessageFromHarmony('${msgJsonStr}');`; 180 | this._evaluateJavascript(JSCommand); 181 | } 182 | 183 | private _evaluateJavascript(javascriptCommand: string) { 184 | this.delegate?.evaluateJavascript(javascriptCommand); 185 | } 186 | 187 | private _log(action: string, message: Map) { 188 | if (!_logging) { 189 | return; 190 | } 191 | const msgJsonStr = WebViewJavascriptBridgeTools.jsonStringify(message); 192 | if (msgJsonStr.length) { 193 | if (msgJsonStr.length > _logMaxLength) { 194 | console.log(_LogTAG, `类型:${action},消息[略]:${msgJsonStr.substring(0, _logMaxLength)}`); 195 | } else { 196 | console.log(_LogTAG, `类型:${action},消息[全]:${msgJsonStr}`); 197 | } 198 | } 199 | } 200 | } -------------------------------------------------------------------------------- /webview_javascript_bridge/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 ZhangYue. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | Apache License, Version 2.0 16 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 17 | 18 | 1. Definitions. 19 | 20 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 23 | 24 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 25 | 26 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 31 | 32 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 33 | 34 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 35 | 36 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 37 | 38 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 39 | 40 | 2. Grant of Copyright License. 41 | 42 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 43 | 44 | 3. Grant of Patent License. 45 | 46 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 47 | 48 | 4. Redistribution. 49 | 50 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 51 | 1.You must give any other recipients of the Work or Derivative Works a copy of this License; and 52 | 2.You must cause any modified files to carry prominent notices stating that You changed the files; and 53 | 3.You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 54 | 4.If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 55 | 56 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 57 | 58 | 5. Submission of Contributions. 59 | 60 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 61 | 62 | 6. Trademarks. 63 | 64 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 65 | 66 | 7. Disclaimer of Warranty. 67 | 68 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 69 | 70 | 8. Limitation of Liability. 71 | 72 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 73 | 74 | 9. Accepting Warranty or Additional Liability. 75 | 76 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 77 | 78 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # WebViewJavascriptBridge 3 | 4 | ## 背景介绍 5 | WebViewJavascriptBridge 在 HarmonyOS 上的实现。 6 | 7 | 本库基于 iOS/Mac 开发者经常会用到的 [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge) 进行分析,然后在鸿蒙上实现。 8 | 9 | 通过本库可以让鸿蒙原生与 JavaScript 完成交互,方便的相互调用彼此函数。 10 | 11 | [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge) 是盛名已久的 JSBridge 库,早在 2011 年就被其作者 Marcus Westin 发布到 GitHub 上,因其代码逻辑清晰,风格良好,加上自身代码量比较小使得其源码阅读非常轻松,广为流传…… ~~吧啦吧啦吧啦~~ 12 | 13 | 主要是现有项目(iOS、Android、H5 )均使用了此库的逻辑,鸿蒙上的开发也不会另起炉灶,于是就有了本库。 14 | 15 | ![demo](./wvjb_demo.gif) 16 | ## 相关特性 17 | * 适配 HarmonyOS,API 10+ 18 | * 逻辑简单,使用方便,不影响原先 Web 组件 19 | * 支持控制台打印调试 20 | * 使用方式与开发 iOS/Mac 类似 21 | * ... 22 | 23 | 24 | 25 | ## 安装使用 26 | ohpm 安装: 27 | ``` 28 | ohpm install @yue/webview_javascript_bridge 29 | ``` 30 | 或者 `oh-package.json5` 中有本库的依赖。 31 | ``` 32 | "dependencies": { 33 | "@yue/webview_javascript_bridge": "^1.0.0" 34 | } 35 | ``` 36 | 37 | 使用: 38 | ### 鸿蒙端: 39 | 1. 在 Web 组件的 `onControllerAttached` 回调中创建 `WebViewJavascriptBridge` 对象,然后在 `onLoadIntercept` 中响应 bridge 拦截事件,这样桥接就已经建立了。 40 | 41 | ```typescript 42 | Web({ 43 | xxxxx 44 | }) 45 | .onControllerAttached(() => { 46 | // WebviewController 载入完成后,设置 WebViewJavascriptBridge 47 | this.bridge = new WebViewJavascriptBridge(this.webviewController); 48 | }) 49 | .onLoadIntercept((event) => { 50 | const url = event.data.getRequestUrl(); 51 | // 符合 WebViewJavascriptBridge 的链接规则才拦截 52 | if (this.bridge?.bridgeInterceptWithURL(url)) { 53 | return true; 54 | } 55 | return false; 56 | }) 57 | ``` 58 | 59 | 2. 原生注册方法,供 JS 调用 60 | 61 | ```typescript 62 | // 注册 getNativeInfo 方法,供 JS 调用 63 | // data 为 JS 调用此方法时传递的参数 64 | // responseCallback() 里的 jsonStr 则返回给 JS 65 | this.bridge.registerHandler('getNativeInfo', 66 | (data: Object, responseCallback: WVJBResponseCallback) => { 67 | responseCallback(jsonStr) 68 | }); 69 | ``` 70 | 71 | 3. H5 已有方法,原生调用 72 | 73 | ```typescript 74 | // JS 已注册过 getH5Info 方法,原生来调用 75 | // responseData 为 JS 给的数据 76 | this.bridge.callHandler({handlerName: 'getH5Info', 77 | responseCallback: (responseData) => { 78 | this.toast("原生调用 getH5Info \n接收到结果:" + 79 | WebViewJavascriptBridgeTools.jsonStringify(responseData)); 80 | } 81 | }) 82 | ``` 83 | 84 | ### JS 端 85 | ```javascript 86 | // 初始化 WebViewJavascriptBridge 87 | function setupWebViewJavascriptBridge(callback) { 88 | if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } 89 | if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } 90 | window.WVJBCallbacks = [callback]; 91 | var WVJBIframe = document.createElement('iframe'); 92 | WVJBIframe.style.display = 'none'; 93 | WVJBIframe.src = 'https://__bridge_loaded__'; 94 | document.documentElement.appendChild(WVJBIframe); 95 | setTimeout(function () { document.documentElement.removeChild(WVJBIframe) }, 0) 96 | } 97 | 98 | // 注册方法,给原生调用 99 | setupWebViewJavascriptBridge(function (bridge) { 100 | bridge.registerHandler('getH5Info', function (data, responseCallback) { 101 | console.log('原生调用 getH5Info 方法传递的参数', JSON.stringify(data)) 102 | // JS 返回给原生的数据 103 | var responseData = { 'H5Info': 'xxxxx' } 104 | responseCallback(responseData) 105 | }) 106 | }) 107 | 108 | // JS 调用原生端注册的方法 109 | function testGetNativeInfo() { 110 | setupWebViewJavascriptBridge(function(bridge) { 111 | bridge.callHandler('getNativeInfo', { 'key': 'value' }, function (response) { 112 | console.log('JS 获取到原生侧给的数据', response) 113 | }) 114 | }) 115 | } 116 | ``` 117 | 118 | ## 详细示例 119 | ### 鸿蒙原生侧 120 | 确保 oh-package.json5 中有本库的依赖。 121 | ``` 122 | "dependencies": { 123 | "@yue/webview_javascript_bridge": "^1.0.0" 124 | } 125 | ``` 126 | 127 | ets 文件 128 | ```typescript 129 | import { webview } from '@kit.ArkWeb'; 130 | import { 131 | WebViewJavascriptBridge, 132 | WebViewJavascriptBridgeTools, 133 | WVJBResponseCallback, 134 | } from '@yue/WebViewJavascriptBridge'; 135 | import { promptAction } from '@kit.ArkUI'; 136 | 137 | @Entry 138 | @Component 139 | struct Index { 140 | // WebviewController 141 | private _controller: webview.WebviewController = new webview.WebviewController(); 142 | // WebViewJavascriptBridge 桥接 143 | private _bridge: WebViewJavascriptBridge | undefined; 144 | 145 | build() { 146 | Column() { 147 | this.webView(); 148 | this.actionView(); 149 | } 150 | .width('100%') 151 | .height('100%') 152 | } 153 | 154 | @Builder 155 | private actionView() { 156 | Column() { 157 | Row() { 158 | Button('h5注册方法,原生从 h5 获取信息').onClick(() => { 159 | this._getH5Info(); 160 | }) 161 | } 162 | .width('100%') 163 | .height('30%') 164 | .backgroundColor(Color.Gray) 165 | } 166 | 167 | @Builder 168 | private webView() { 169 | Web({ 170 | src: $rawfile('index.html'), 171 | controller: this._controller, 172 | }) 173 | .width('100%') 174 | .height('70%') 175 | .domStorageAccess(true) 176 | .onControllerAttached(() => { 177 | // WebviewController 载入完成后,设置 WebViewJavascriptBridge 178 | this.setupWebViewJavascriptBridge(); 179 | }) 180 | .onAlert((event) => { 181 | AlertDialog.show({ message: event?.message }) 182 | return false; 183 | }) 184 | .onLoadIntercept((event) => { 185 | const url = event.data.getRequestUrl(); 186 | // 符合 WebViewJavascriptBridge 的链接规则才拦截 187 | if (this._bridge?.bridgeInterceptWithURL(url)) { 188 | return true; 189 | } 190 | return false; 191 | }); 192 | } 193 | 194 | /** 195 | * 设置 jsBridge 196 | */ 197 | private setupWebViewJavascriptBridge() { 198 | // 开启调试,控制台打印信息,默认无调试 199 | WebViewJavascriptBridge.enableLogging(); 200 | // 创建 WebViewJavascriptBridge 201 | this._bridge = new WebViewJavascriptBridge(this._controller); 202 | // 创建后,可注册相关方法 203 | this._bridge.registerHandler('getNativeInfo', (data: Object, responseCallback: WVJBResponseCallback) => { 204 | if (data) { 205 | this._toast("JS 调用 getNativeInfo \n原生接收到参数:" + WebViewJavascriptBridgeTools.jsonStringify(data)); 206 | } 207 | // 返回数据 208 | let map: Record> = { 209 | "deviceInfo": { 210 | "types": "harmony", 211 | "debug": "1", 212 | }, 213 | "userInfo": { 214 | "userId": "666666", 215 | "member_name": "用户名", 216 | } 217 | } 218 | const jsonStr: string = WebViewJavascriptBridgeTools.jsonStringify(map); 219 | responseCallback(jsonStr); 220 | }); 221 | } 222 | 223 | private _getH5Info() { 224 | let arg = new Map>([ 225 | ["key", "1"], 226 | ]); 227 | 228 | this._bridge?.callHandler({ 229 | handlerName: 'getH5Info', 230 | data: arg, 231 | responseCallback: (responseData) => { 232 | this._toast(WebViewJavascriptBridgeTools.jsonStringify(responseData)); 233 | } 234 | }); 235 | } 236 | 237 | private _toast(msg: string) { 238 | promptAction.showToast({ 239 | message: msg, 240 | duration: 4000 241 | }); 242 | } 243 | } 244 | ``` 245 | 246 | ### JS侧 247 | ```javascript 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | WebViewJavascriptBridge 258 | 259 | 260 | 261 | 309 | 310 |

WebViewJavascriptBridge

311 | 312 |
313 |
314 |
315 | 316 | 317 | 318 | 319 | ``` 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /webview_javascript_bridge/README.md: -------------------------------------------------------------------------------- 1 | 2 | # WebViewJavascriptBridge 3 | 4 | ## 背景介绍 5 | WebViewJavascriptBridge 在 HarmonyOS 上的实现。 6 | 7 | 本库基于 iOS/Mac 开发者经常会用到的 [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge) 进行分析,然后在鸿蒙上实现。 8 | 9 | 通过本库可以让鸿蒙原生与 JavaScript 完成交互,方便的相互调用彼此函数。 10 | 11 | [WebViewJavascriptBridge](https://github.com/marcuswestin/WebViewJavascriptBridge) 是盛名已久的 JSBridge 库,早在 2011 年就被其作者 Marcus Westin 发布到 GitHub 上,因其代码逻辑清晰,风格良好,加上自身代码量比较小使得其源码阅读非常轻松,广为流传…… ~~吧啦吧啦吧啦~~ 12 | 13 | 主要是现有项目(iOS、Android、H5 )均使用了此库的逻辑,鸿蒙上的开发也不会另起炉灶,于是就有了本库。 14 | 15 | ![demo](../wvjb_demo.gif) 16 | 17 | ## 相关特性 18 | * 适配 HarmonyOS,API 10+ 19 | * 逻辑简单,使用方便,不影响原先 Web 组件 20 | * 支持控制台打印调试 21 | * 使用方式与开发 iOS/Mac 类似 22 | * ... 23 | 24 | 25 | 26 | ## 安装使用 27 | ohpm 安装: 28 | ``` 29 | ohpm install @yue/webview_javascript_bridge 30 | ``` 31 | 或者 `oh-package.json5` 中有本库的依赖。 32 | ``` 33 | "dependencies": { 34 | "@yue/webview_javascript_bridge": "^1.0.0" 35 | } 36 | ``` 37 | 38 | 使用: 39 | ### 鸿蒙端: 40 | 1. 在 Web 组件的 `onControllerAttached` 回调中创建 `WebViewJavascriptBridge` 对象,然后在 `onLoadIntercept` 中响应 bridge 拦截事件,这样桥接就已经建立了。 41 | 42 | ```typescript 43 | Web({ 44 | xxxxx 45 | }) 46 | .onControllerAttached(() => { 47 | // WebviewController 载入完成后,设置 WebViewJavascriptBridge 48 | this.bridge = new WebViewJavascriptBridge(this.webviewController); 49 | }) 50 | .onLoadIntercept((event) => { 51 | const url = event.data.getRequestUrl(); 52 | // 符合 WebViewJavascriptBridge 的链接规则才拦截 53 | if (this.bridge?.bridgeInterceptWithURL(url)) { 54 | return true; 55 | } 56 | return false; 57 | }) 58 | ``` 59 | 60 | 2. 原生注册方法,供 JS 调用 61 | 62 | ```typescript 63 | // 注册 getNativeInfo 方法,供 JS 调用 64 | // data 为 JS 调用此方法时传递的参数 65 | // responseCallback() 里的 jsonStr 则返回给 JS 66 | this.bridge.registerHandler('getNativeInfo', 67 | (data: Object, responseCallback: WVJBResponseCallback) => { 68 | responseCallback(jsonStr) 69 | }); 70 | ``` 71 | 72 | 3. H5 已有方法,原生调用 73 | 74 | ```typescript 75 | // JS 已注册过 getH5Info 方法,原生来调用 76 | // responseData 为 JS 给的数据 77 | this.bridge.callHandler({handlerName: 'getH5Info', 78 | responseCallback: (responseData) => { 79 | this.toast("原生调用 getH5Info \n接收到结果:" + 80 | WebViewJavascriptBridgeTools.jsonStringify(responseData)); 81 | } 82 | }) 83 | ``` 84 | 85 | ### JS 端 86 | ```javascript 87 | // 初始化 WebViewJavascriptBridge 88 | function setupWebViewJavascriptBridge(callback) { 89 | if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); } 90 | if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); } 91 | window.WVJBCallbacks = [callback]; 92 | var WVJBIframe = document.createElement('iframe'); 93 | WVJBIframe.style.display = 'none'; 94 | WVJBIframe.src = 'https://__bridge_loaded__'; 95 | document.documentElement.appendChild(WVJBIframe); 96 | setTimeout(function () { document.documentElement.removeChild(WVJBIframe) }, 0) 97 | } 98 | 99 | // 注册方法,给原生调用 100 | setupWebViewJavascriptBridge(function (bridge) { 101 | bridge.registerHandler('getH5Info', function (data, responseCallback) { 102 | console.log('原生调用 getH5Info 方法传递的参数', JSON.stringify(data)) 103 | // JS 返回给原生的数据 104 | var responseData = { 'H5Info': 'xxxxx' } 105 | responseCallback(responseData) 106 | }) 107 | }) 108 | 109 | // JS 调用原生端注册的方法 110 | function testGetNativeInfo() { 111 | setupWebViewJavascriptBridge(function(bridge) { 112 | bridge.callHandler('getNativeInfo', { 'key': 'value' }, function (response) { 113 | console.log('JS 获取到原生侧给的数据', response) 114 | }) 115 | }) 116 | } 117 | ``` 118 | 119 | ## 详细示例 120 | ### 鸿蒙原生侧 121 | 确保 oh-package.json5 中有本库的依赖。 122 | ``` 123 | "dependencies": { 124 | "@yue/webview_javascript_bridge": "^1.0.0" 125 | } 126 | ``` 127 | 128 | ets 文件 129 | ```typescript 130 | import { webview } from '@kit.ArkWeb'; 131 | import { 132 | WebViewJavascriptBridge, 133 | WebViewJavascriptBridgeTools, 134 | WVJBResponseCallback, 135 | } from '@yue/WebViewJavascriptBridge'; 136 | import { promptAction } from '@kit.ArkUI'; 137 | 138 | @Entry 139 | @Component 140 | struct Index { 141 | // WebviewController 142 | private _controller: webview.WebviewController = new webview.WebviewController(); 143 | // WebViewJavascriptBridge 桥接 144 | private _bridge: WebViewJavascriptBridge | undefined; 145 | 146 | build() { 147 | Column() { 148 | this.webView(); 149 | this.actionView(); 150 | } 151 | .width('100%') 152 | .height('100%') 153 | } 154 | 155 | @Builder 156 | private actionView() { 157 | Column() { 158 | Row() { 159 | Button('h5注册方法,原生从 h5 获取信息').onClick(() => { 160 | this._getH5Info(); 161 | }) 162 | } 163 | .width('100%') 164 | .height('30%') 165 | .backgroundColor(Color.Gray) 166 | } 167 | 168 | @Builder 169 | private webView() { 170 | Web({ 171 | src: $rawfile('index.html'), 172 | controller: this._controller, 173 | }) 174 | .width('100%') 175 | .height('70%') 176 | .domStorageAccess(true) 177 | .onControllerAttached(() => { 178 | // WebviewController 载入完成后,设置 WebViewJavascriptBridge 179 | this.setupWebViewJavascriptBridge(); 180 | }) 181 | .onAlert((event) => { 182 | AlertDialog.show({ message: event?.message }) 183 | return false; 184 | }) 185 | .onLoadIntercept((event) => { 186 | const url = event.data.getRequestUrl(); 187 | // 符合 WebViewJavascriptBridge 的链接规则才拦截 188 | if (this._bridge?.bridgeInterceptWithURL(url)) { 189 | return true; 190 | } 191 | return false; 192 | }); 193 | } 194 | 195 | /** 196 | * 设置 jsBridge 197 | */ 198 | private setupWebViewJavascriptBridge() { 199 | // 开启调试,控制台打印信息,默认无调试 200 | WebViewJavascriptBridge.enableLogging(); 201 | // 创建 WebViewJavascriptBridge 202 | this._bridge = new WebViewJavascriptBridge(this._controller); 203 | // 创建后,可注册相关方法 204 | this._bridge.registerHandler('getNativeInfo', (data: Object, responseCallback: WVJBResponseCallback) => { 205 | if (data) { 206 | this._toast("JS 调用 getNativeInfo \n原生接收到参数:" + WebViewJavascriptBridgeTools.jsonStringify(data)); 207 | } 208 | // 返回数据 209 | let map: Record> = { 210 | "deviceInfo": { 211 | "types": "harmony", 212 | "debug": "1", 213 | }, 214 | "userInfo": { 215 | "userId": "666666", 216 | "member_name": "用户名", 217 | } 218 | } 219 | const jsonStr: string = WebViewJavascriptBridgeTools.jsonStringify(map); 220 | responseCallback(jsonStr); 221 | }); 222 | } 223 | 224 | private _getH5Info() { 225 | let arg = new Map>([ 226 | ["key", "1"], 227 | ]); 228 | 229 | this._bridge?.callHandler({ 230 | handlerName: 'getH5Info', 231 | data: arg, 232 | responseCallback: (responseData) => { 233 | this._toast(WebViewJavascriptBridgeTools.jsonStringify(responseData)); 234 | } 235 | }); 236 | } 237 | 238 | private _toast(msg: string) { 239 | promptAction.showToast({ 240 | message: msg, 241 | duration: 4000 242 | }); 243 | } 244 | } 245 | ``` 246 | 247 | ### JS侧 248 | ```javascript 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | WebViewJavascriptBridge 259 | 260 | 261 | 262 | 310 | 311 |

WebViewJavascriptBridge

312 | 313 |
314 |
315 |
316 | 317 | 318 | 319 | 320 | ``` 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | -------------------------------------------------------------------------------- /webview_javascript_bridge/src/main/ets/WebViewJavascriptBridgeTools.ets: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Yue 2024. 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * Created by Yue on 2024/6/24. 16 | */ 17 | 18 | import { HashMap } from '@kit.ArkTS' 19 | 20 | export class WebViewJavascriptBridgeTools { 21 | /** 22 | * 判断数据是否为空 23 | * @param value 24 | * @returns 25 | */ 26 | public static isEmpty(value: Object | null | undefined): boolean { 27 | if (value === null || value === undefined || value === '') { 28 | return true 29 | } 30 | if (Array.isArray(value)) { 31 | return value.length === 0 32 | } 33 | if (value instanceof Map || value instanceof Set) { 34 | return value.size === 0 35 | } 36 | if (value?.toLocaleString() === '[object Object]') { 37 | return JSON.stringify(value) === "{}" 38 | } 39 | return false 40 | } 41 | 42 | /** 43 | * 任意类型转 json 字符串,不能转的返回空字符串 "" 44 | * @param json 45 | * @returns 46 | */ 47 | public static jsonStringify(json: Object | undefined | null): string { 48 | if (json instanceof Map) { 49 | let obj: object = WebViewJavascriptBridgeTools.mapToObject(json); 50 | return JSON.stringify(obj); 51 | } 52 | try { 53 | let jsonStr = JSON.stringify(json); 54 | return (jsonStr.length > 0) ? jsonStr : ""; 55 | } catch (e) { 56 | return ""; 57 | } 58 | } 59 | 60 | /// json 转 字典 Map 61 | public static jsonToMap(obj: Object | undefined | null): Map { 62 | let result: Map = new Map(); 63 | // 传入的 obj 为空 64 | if (obj === undefined || obj === null) { 65 | return result; 66 | } 67 | 68 | // 传入的是 字符串 69 | if (typeof obj === 'string') { 70 | // 尝试用 json 对象接收 71 | try { 72 | let jsonObj: Object = JSON.parse(obj); 73 | // json 对象 是 字典 74 | if (jsonObj instanceof Map) { 75 | let jsonMap: Map = jsonObj; 76 | jsonMap.forEach((value, key) => { 77 | result.set(key.toString(), value); 78 | }); 79 | } 80 | // json 对象 不是 字典 81 | else { 82 | try { 83 | let resultMap: Map = new Map(); 84 | let objMap: Map = new Map(Object.entries(obj)); 85 | objMap.forEach((value, key) => { 86 | resultMap.set(key.toString(), value); 87 | }); 88 | return resultMap; 89 | } catch (e) { 90 | } 91 | } 92 | } catch (e) { 93 | } 94 | } 95 | // 传入的本身就是 map 96 | else if (obj instanceof Map) { 97 | let objMap: Map = obj; 98 | objMap.forEach((value, key) => { 99 | result.set(key.toString(), value); 100 | }); 101 | } 102 | // 其他情况 103 | else { 104 | try { 105 | let resultMap: Map = new Map(); 106 | let objMap: Map = new Map(Object.entries(obj)); 107 | objMap.forEach((value, key) => { 108 | resultMap.set(key.toString(), value); 109 | }); 110 | return resultMap; 111 | } catch (e) { 112 | } 113 | } 114 | return result; 115 | } 116 | 117 | /** 118 | * json 转 数组 Array 119 | * @param obj 任意类型数据 Object 120 | * @returns 数组 Array 121 | */ 122 | public static jsonToObjArr(obj: Object | undefined | null): Array { 123 | let result: Array = new Array(); 124 | // 传入的 obj 为空 125 | if (obj === undefined || obj === null) { 126 | return result; 127 | } 128 | 129 | // 传入的是 字符串 130 | if (typeof obj === 'string') { 131 | // 尝试用 json 对象接收 132 | try { 133 | let jsonObj: Object = JSON.parse(obj); 134 | // json 对象 是 数组 135 | if (jsonObj instanceof Array) { 136 | result = jsonObj; 137 | } 138 | // json 对象 是 字符串 139 | else if (typeof jsonObj === "string") { 140 | jsonObj = JSON.parse(jsonObj); 141 | if (jsonObj instanceof Array) { 142 | result = jsonObj; 143 | } else if (typeof jsonObj === "string") { 144 | jsonObj = JSON.parse(jsonObj); 145 | if (jsonObj instanceof Array) { 146 | result = jsonObj; 147 | } 148 | } 149 | } else { 150 | 151 | } 152 | } catch (e) { 153 | } 154 | } 155 | // 传入的本身就是 arr 156 | else if (obj instanceof Array) { 157 | result = obj; 158 | } 159 | // 其他情况 160 | else { 161 | try { 162 | let resultArr: Array = new Array(); 163 | let objArr: Array = new Array(Object.values(obj)); 164 | objArr.forEach((value, index) => { 165 | resultArr.push(value); 166 | }); 167 | return resultArr; 168 | } catch (e) { 169 | } 170 | } 171 | return result; 172 | } 173 | 174 | /** 175 | * 传入任意 Object 类型,获得 字符串 类型 176 | * @param obj 任意类型数据 177 | * @param def 默认值 178 | * @returns 字符串 179 | */ 180 | public static objToStr(obj: Object | undefined | null, def?: string): string { 181 | let result: string = def ?? ""; 182 | // 传入的 obj 为空 183 | if (obj === undefined || obj === null || obj === '') { 184 | return result; 185 | } 186 | 187 | // 传入的是 字符串 188 | if (typeof obj === 'string') { 189 | return obj; 190 | } 191 | // 是 bool 类型的,true 为 转为 "1", 否则为 "0" 192 | else if (typeof obj === 'boolean') { 193 | return obj === true ? "1" : "0"; 194 | } 195 | // 是 number 类型的 196 | else if (typeof obj === 'number') { 197 | return obj.toString(); 198 | } 199 | // 若是 Map 类型的 200 | else if (obj instanceof Map) { 201 | let jsonStr = WebViewJavascriptBridgeTools.jsonStringify(obj); 202 | return (jsonStr.length > 0) ? jsonStr : result; 203 | } 204 | // 若是 Array 类型 205 | else if (obj instanceof Array) { 206 | let jsonStr = WebViewJavascriptBridgeTools.jsonStringify(obj); 207 | return (jsonStr.length > 0) ? jsonStr : result; 208 | } 209 | // 其他情况 210 | else { 211 | let jsonStr = WebViewJavascriptBridgeTools.jsonStringify(obj); 212 | return (jsonStr.length > 0) ? jsonStr : result; 213 | } 214 | } 215 | 216 | /** 217 | * 传入任意 Object 类型,获得 boolean 布尔 类型 218 | * @param obj 任意类型数据 219 | * @param def 默认值 220 | * @returns 字符串 221 | */ 222 | public static objToBool(obj: Object | undefined | null, def?: boolean): boolean { 223 | let result: boolean = def ?? false; 224 | // 传入的 obj 为空 225 | if (obj === undefined || obj === null) { 226 | return result; 227 | } 228 | 229 | // 传入的是 字符串 230 | if (typeof obj === 'boolean') { 231 | return obj; 232 | } 233 | // 是 字符串 类型的,"1"、"true"、"TRUE"、"yes"、"YES" 为 true 234 | else if (typeof obj === 'string') { 235 | return (obj === "1" || obj.toLowerCase() === "true" || obj.toLowerCase() === "yes") ? true : result; 236 | } 237 | // 是 number 类型的 (仅为 1 时才是 true) 238 | else if (typeof obj === 'number') { 239 | // 仅为 1 时才是 true 240 | return (obj === 1 || obj === 1.0) ? true : result; 241 | // // 非 0 即是 true 242 | // return (obj !== 0 || obj !== 0.0) ? true : result; 243 | } 244 | // 其他情况 245 | else { 246 | 247 | } 248 | return result; 249 | } 250 | 251 | /** 252 | * 传入任意 Object 类型,获得 number 数值 类型 253 | * @param obj 任意类型数据 254 | * @param def 默认值 255 | * @returns number 数值 256 | */ 257 | public static objToNum(obj: Object | undefined | null, def?: number): number { 258 | let result: number = def ?? 0; 259 | // 传入的 obj 为空 260 | if (obj === undefined || obj === null) { 261 | return result; 262 | } 263 | 264 | // 传入的是 字符串 265 | if (typeof obj === 'number') { 266 | return obj; 267 | } 268 | // 是 字符串 类型的,"1"、"true"、"TRUE"、"yes"、"YES" 为 true 269 | else if (typeof obj === 'string') { 270 | return Number(obj).valueOf(); 271 | } 272 | // 是 number 类型的 273 | else if (typeof obj === 'boolean') { 274 | return (obj === true) ? 1 : result; 275 | } 276 | // 其他情况 277 | else { 278 | 279 | } 280 | return result; 281 | } 282 | 283 | /** 284 | * 传入任意 Object 类型,获得 Map 字典类型 285 | * @param obj 任意类型数据 286 | * @param def 默认值 287 | * @returns Map 字典类型 288 | */ 289 | public static objToObjMap(obj: Object | undefined | null, def?: Map): Map { 290 | let result: Map = def ?? new Map(); 291 | // 传入的 obj 为空 292 | if (obj === undefined || obj === null) { 293 | return result; 294 | } 295 | 296 | // 是 Map 类型的 297 | if (obj instanceof Map || obj instanceof HashMap) { 298 | let resultMap: Map = new Map(); 299 | let objMap: Map = obj as Map; 300 | objMap.forEach((value, key) => { 301 | resultMap.set(key, value); 302 | }); 303 | return resultMap; 304 | } 305 | // 是 字符串类型 306 | else if (typeof obj === 'string') { 307 | return (obj.length > 0) ? WebViewJavascriptBridgeTools.jsonToMap(obj) : result; 308 | } 309 | // 其他类型 310 | else { 311 | try { 312 | return new Map(Object.entries(obj)); 313 | } catch (e) { 314 | } 315 | } 316 | return result; 317 | } 318 | 319 | /** 320 | * 传入任意 Object 类型,获得 Map 字典类型 321 | * @param obj 任意类型数据 322 | * @param def 默认值 323 | * @returns Map 字典类型 324 | */ 325 | public static objToStrKeyMap(obj: Object | undefined | null, def?: Map): Map { 326 | let result: Map = def ?? new Map(); 327 | // 传入的 obj 为空 328 | if (obj === undefined || obj === null) { 329 | return result; 330 | } 331 | 332 | // 是 Map 类型的 333 | if (obj instanceof Map) { 334 | let resultMap: Map = new Map(); 335 | let objMap: Map = obj as Map; 336 | objMap.forEach((value, key) => { 337 | resultMap.set(key.toString(), value); 338 | }); 339 | return resultMap; 340 | } 341 | // 是 字符串类型 342 | else if (typeof obj === 'string') { 343 | return (obj.length > 0) ? WebViewJavascriptBridgeTools.jsonToMap(obj) : result; 344 | } 345 | // 其他类型 346 | else { 347 | try { 348 | let resultMap: Map = new Map(); 349 | let objMap: Map = new Map(Object.entries(obj)); 350 | objMap.forEach((value, key) => { 351 | resultMap.set(key.toString(), value); 352 | }); 353 | return resultMap; 354 | } catch (e) { 355 | } 356 | } 357 | return result; 358 | } 359 | 360 | /// 获取一个 Map 里面 的 原始值,可能是空 undefined,支持 aaa/bbb/ccc 多级取值 361 | public static getMapOriginValue(map: Map | undefined, key: string): Object | undefined { 362 | // map 无数据 363 | if (map === undefined || map === null) { 364 | return undefined; 365 | } 366 | // map 有数据 367 | if (key.match('/')) { 368 | const keyList = key.split('/'); 369 | return WebViewJavascriptBridgeTools._mapGetValue(map, keyList); 370 | } else { 371 | return map.get(key); 372 | } 373 | } 374 | 375 | /// 获取一个 Map 里面 string 类型的值 376 | public static getMapStringValue(map: Map | undefined, key: string, def?: string): string { 377 | let value = WebViewJavascriptBridgeTools.getMapOriginValue(map, key); 378 | if (value === undefined || value === null) { 379 | return def ?? ""; 380 | } else { 381 | return WebViewJavascriptBridgeTools.objToStr(value, def); 382 | } 383 | } 384 | 385 | /// 获取一个 Map 里面 number 类型的值 386 | public static getMapNumberValue(map: Map | undefined, key: string, def?: number): number { 387 | let value = WebViewJavascriptBridgeTools.getMapOriginValue(map, key) 388 | if (value === undefined || value === null) { 389 | return def ?? 0; 390 | } else { 391 | return WebViewJavascriptBridgeTools.objToNum(value, def); 392 | } 393 | } 394 | 395 | /// 获取一个 Map 里面 number 类型的值 396 | public static getMapBoolValue(map: Map | undefined, key: string, def?: boolean): boolean { 397 | let value = WebViewJavascriptBridgeTools.getMapOriginValue(map, key) 398 | if (value === undefined || value === null) { 399 | return def ?? false; 400 | } else { 401 | return WebViewJavascriptBridgeTools.objToBool(value, def); 402 | } 403 | } 404 | 405 | /** 406 | * Map 转 Object 407 | * @param map 408 | * @returns 409 | */ 410 | public static mapToObject(map: Map): object { 411 | let obj: object = new Object(); 412 | if (map instanceof Map) { 413 | map.forEach((value: Object, key: Object) => { 414 | if (value instanceof Map) { 415 | obj[key.toString()] = WebViewJavascriptBridgeTools.mapToObject(value); 416 | } else { 417 | obj[key.toString()] = value; 418 | } 419 | }); 420 | } 421 | return obj; 422 | } 423 | 424 | /** 425 | * 内部方法,获取一个 字典 下,对应 keys 顺序的 值 426 | * @param map map 对象 427 | * @param keys [aaa, bbb, ccc] 多级 key 拆分的数组 428 | * @returns 结果 429 | */ 430 | private static _mapGetValue(map: Object | undefined, keys: Array): Object | undefined { 431 | // map 无数据 432 | if (map === undefined || map === null) { 433 | return undefined; 434 | } 435 | // map 转 newMap 436 | const newMap = WebViewJavascriptBridgeTools.objToObjMap(map); 437 | // newMap 无数据 438 | if (newMap.size === 0) { 439 | return undefined; 440 | } 441 | // newMap 有数据 442 | let tmpMap = newMap; 443 | for (let index = 0; index < keys.length; index++) { 444 | const key = keys[index]; 445 | const getValue = tmpMap.get(key); 446 | // 最后一个了 447 | if (index == keys.length - 1) { 448 | return getValue; 449 | } else { 450 | if (getValue === undefined || getValue === null) { 451 | return undefined; 452 | } 453 | tmpMap = WebViewJavascriptBridgeTools.objToObjMap(getValue); 454 | } 455 | } 456 | return undefined; 457 | } 458 | } --------------------------------------------------------------------------------