├── .gitignore ├── AppScope ├── app.json5 └── resources │ └── base │ ├── element │ └── string.json │ └── media │ └── app_icon.png ├── README.md ├── build-profile.json5 ├── entry ├── .gitignore ├── build-profile.json5 ├── hvigorfile.ts ├── oh-package-lock.json5 ├── oh-package.json5 └── src │ ├── main │ ├── ets │ │ ├── bridge │ │ │ ├── JsBridge.ets │ │ │ ├── JsBridge2.ets │ │ │ └── JsBridgeNamespace.ets │ │ ├── entryability │ │ │ └── EntryAbility.ets │ │ ├── pages │ │ │ ├── Index.ets │ │ │ ├── NativeAndJsCallsPage.ets │ │ │ ├── TestPage.ets │ │ │ ├── UseInComponentsPage.ets │ │ │ └── UseInJs2Page.ets │ │ └── utils │ │ │ └── LogUtils.ets │ ├── module.json5 │ └── resources │ │ ├── base │ │ ├── element │ │ │ ├── color.json │ │ │ └── string.json │ │ ├── media │ │ │ └── icon.png │ │ └── profile │ │ │ └── main_pages.json │ │ └── rawfile │ │ ├── dsBridge3.0.js │ │ ├── dsbridge2.0.js │ │ ├── index.html │ │ ├── new-page.html │ │ ├── use-in-components.html │ │ └── use-in-js2.html │ └── ohosTest │ ├── ets │ ├── test │ │ ├── Ability.test.ets │ │ └── List.test.ets │ ├── testability │ │ ├── TestAbility.ets │ │ └── pages │ │ │ └── Index.ets │ └── testrunner │ │ └── OpenHarmonyTestRunner.ts │ ├── module.json5 │ └── resources │ └── base │ ├── element │ ├── color.json │ └── string.json │ ├── media │ └── icon.png │ └── profile │ └── test_pages.json ├── hvigor └── hvigor-config.json5 ├── hvigorfile.ts ├── library ├── .gitignore ├── BuildProfile.ets ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── build-profile.json5 ├── example │ └── README.md ├── hvigorfile.ts ├── index.ets ├── oh-package-lock.json5 ├── oh-package.json5 └── src │ └── main │ ├── ets │ ├── components │ │ └── mainpage │ │ │ └── MainPage.ets │ ├── core │ │ ├── BaseBridge.ts │ │ ├── Entity.ts │ │ ├── WebViewControllerProxy.ets │ │ └── WebViewInterface.ts │ ├── utils │ │ ├── LogUtils.ts │ │ └── ToastUtils.ts │ └── wait │ │ ├── BaseSendable.ets │ │ └── TaskWait.ets │ ├── module.json5 │ └── resources │ └── base │ └── element │ └── string.json ├── libs └── dsbridge.har ├── oh-package-lock.json5 └── oh-package.json5 /.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 | /config 13 | /dependencies -------------------------------------------------------------------------------- /AppScope/app.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "bundleName": "com.hzw.dsbridge", 4 | "vendor": "example", 5 | "versionCode": 1000000, 6 | "versionName": "1.0.0", 7 | "icon": "$media:app_icon", 8 | "label": "$string:app_name" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AppScope/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "app_name", 5 | "value": "DSBridge-demo" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /AppScope/resources/base/media/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/751496032/DSBridge-HarmonyOS/7515ee4037c91959c4a7e2244802cf67a3e3a7c3/AppScope/resources/base/media/app_icon.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 介绍 3 | 4 | HarmonyOS版的DSBridge,通过本库可以在鸿蒙原生与JavaScript完成交互,可以相互调用彼此的功能。 5 | 6 | 目前兼容Android、iOS第三方DSBridge库的核心功能,基本保持原来的使用方式,后续会持续迭代保持与DSBridge库相同的功能,减少前端和客户端的适配工作,另外也会根据鸿蒙的特性做一些定制需求。 7 | 8 | 特性: 9 | 10 | - **已适配鸿蒙NEXT版本;** 11 | - **支持原生同步方法内执行串行异步并发任务,同步等待异步结果,根据鸿蒙特点设计的需求;** 12 | - **兼容DSBridge2.0与3.0 JS脚本;** 13 | - 支持以类的方式集中统一管理API,也支持原生自定义页面组件直接注册使用; 14 | - 支持同步和异步调用; 15 | - 支持进度回调/回传:一次调用,多次返回; 16 | - 支持API是否存在检测; 17 | - 支持Javascript关闭页面的监听与拦截, 18 | - 支持异常信息监听; 19 | - 支持命名空间API。 20 | 21 | 22 | 23 | DSBridge-HarmonyOS已上架录入到[华为鸿蒙生态伙伴组件专区](https://developer.huawei.com/consumer/cn/market/landing/component) 24 | 25 | ![鸿蒙生态市场](https://gitee.com/common-apps/images/raw/master/oh.png) 26 | 27 | 28 | 29 | 源码: 30 | 31 | * github:[DSBridge-HarmonyOS](https://github.com/751496032/DSBridge-HarmonyOS) 32 | * gitee: [DSBridge-HarmonyOS](https://gitee.com/common-apps/dsbrigde-harmony-os) 33 | * [DSBridge-Android](https://github.com/wendux/DSBridge-Android) 34 | * [DSBridge-IOS](https://github.com/wendux/DSBridge-IOS) 35 | 36 | 37 | >由于DSBridge库作者已停止维护,Android端建议使用 https://github.com/751496032/DSBridge-Android ,目前本人在维护。 38 | 39 | 40 | ## 安装 41 | 42 | 安装库: 43 | 44 | ```text 45 | ohpm install @hzw/ohos-dsbridge 46 | ``` 47 | 48 | 49 | ## 基本用法 50 | 51 | ### ArkTS原生侧 52 | 53 | 1、在原生新建一个类`JsBridge`,实现业务API 54 | , 通过类来集中统一管理API,方法用`@JavaScriptInterface()`标注,是不是很眼熟呢,加一个`@JavaScriptInterface()`标注主要为了使用规范,是自定义的装饰器,与Android保持一致性。 55 | ```typescript 56 | export class JsBridge{ 57 | private cHandler: CompleteHandler = null 58 | 59 | /** 60 | * 同步 61 | * @param p 62 | * @returns 63 | */ 64 | @JavaScriptInterface(false) 65 | testSync(p: string): string { 66 | LogUtils.d("testSync: " + JSON.stringify(p)) 67 | return "hello native" 68 | } 69 | 70 | /** 71 | * 异步 72 | * @param p 73 | * @param handler 74 | */ 75 | @JavaScriptInterface() 76 | testAsync(p: string, handler: CompleteHandler) { 77 | LogUtils.d("testAsync: " + JSON.stringify(p)) 78 | this.cHandler = handler 79 | } 80 | } 81 | ``` 82 | 83 | 如果你不希望用一个类来管理API接口,可以在自定义页面组件Component中直接注册使用,然后在组件内定义API接口。 84 | 85 | ```typescript 86 | @Component 87 | @Entry 88 | struct UseInComponentsPage{ 89 | aboutToAppear() 90 | { 91 | // 在一个组件内只能存在一个无命名空间 92 | this.controller.addJavascriptObject(this) 93 | 94 | } 95 | 96 | @JavaScriptInterface(false) 97 | testComponentSync(args: string): string { 98 | return `组件中的同步方法: ${args}` 99 | } 100 | 101 | @JavaScriptInterface() 102 | testComponentAsync(args: string, handler: CompleteHandler) { 103 | handler.complete(`组件中的异步方法: ${args}`) 104 | } 105 | 106 | } 107 | 108 | 109 | ``` 110 | 111 | **API同步方法是不支持用async/await声明,如果需要在同步方法内执行异步任务,可以使用`taskWait()`函数来加持完成,下面会介绍基本用法**;异步方法的形参`CompleteHandler`,可用于结果异步回调。 112 | 113 | 2、在原生Web组件初始化时,通过`WebViewControllerProxy`类来获取`WebviewController`实例来实现JS注入,然后将其关联到Web组件中,接着将API管理类(JsBridge)关联到`WebViewControllerProxy`中。 114 | 115 | ```typescript 116 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 117 | 118 | aboutToAppear() { 119 | this.controller.addJavascriptObject(new JsBridge()) 120 | } 121 | 122 | 123 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 124 | .javaScriptAccess(true) 125 | .javaScriptProxy(this.controller.getJavaScriptProxy()) 126 | .onAlert((event) => { 127 | // AlertDialog.show({ message: event.message }) 128 | return false 129 | }) 130 | 131 | ) 132 | 133 | ``` 134 | 135 | 3、通过`WebViewControllerProxy`调用JavaScript函数。 136 | 137 | ```typescript 138 | Button("调用js函数-同步") 139 | .onClick(() => { 140 | this.controller.callJs("showAlert", [1, 2, '666'], (v) => { 141 | this.msg = v + "" 142 | }) 143 | }) 144 | 145 | Button("调用js函数-异步") 146 | .onClick(() => { 147 | this.controller.callJs("showAlertAsync", [1, 2, '666'], (v) => { 148 | this.msg = v + "" 149 | }) 150 | }) 151 | } 152 | ``` 153 | 154 | `callJs()`方法有三个形参,第一个是Js注册的函数名称,第二个是Js接收函数的参数,是一个数组类型,第三个是监听Js函数返回结果的函数。 155 | 另外也提供了与Android库一样调用函数`callHandler()`。 156 | 157 | 158 | 如果前端使用的是DSBridge2.0的JS脚本,可以通过supportDS2()方法来兼容,如下: 159 | 160 | ```typescript 161 | aboutToAppear() { 162 | // 如果是使用DSBridge2.0 ,调用supportDS2方法 163 | this.controller.supportDS2(true) 164 | // 以下两种注册方式都可以,任选其一 165 | this.controller.addJavascriptObject(this) 166 | // this.controller.addJavascriptObject(new JsBridge2()) 167 | // DS2.0脚本不支持API命令空间 168 | // this.controller.addJavascriptObject(new JsBridge2(),'js2') 169 | // 开启调试模式 170 | webview.WebviewController.setWebDebuggingAccess(true); 171 | } 172 | ``` 173 | 174 | 175 | 176 | ### JavaScript侧 177 | 178 | 1、在JavaScript中初始化dsBridge,通过cdn或者npm安装都可以。 179 | 180 | 如果项目没有历史包袱,建议直接用`m-dsbridge`包。 181 | 182 | ``` 183 | npm i m-dsbridge 184 | // 或者cdn引入 185 | 186 | ``` 187 | 188 | 也支持直接用原Android或iOS的[DSBridge库](https://github.com/wendux/DSBridge-Android)的JS脚本。 189 | 190 | ```typescript 191 | https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js 192 | ``` 193 | 194 | > 如果m-dsbridge 发生异常,建议切换到 https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js 195 | 196 | 2、通过`dsBridge`对象注册Js函数,供原生调用。 197 | 198 | ```typescript 199 | // 注册同步函数 200 | dsBridge.register('showAlert', function (a, b, c) { 201 | // return "原生调用JS showAlert函数" 202 | alert("原生调用JS showAlert函数" + a + " " + b + " " + c) 203 | return true 204 | }) 205 | 206 | // 注册异步函数 207 | dsBridge.registerAsyn('showAlertAsync', function (a, b, c, callback) { 208 | let counter = 0 209 | let id = setInterval(() => { 210 | if (counter < 5) { 211 | callback(counter, false) 212 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 213 | counter++ 214 | } else { 215 | callback(counter, true) 216 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 217 | clearInterval(id) 218 | } 219 | }, 1000) 220 | 221 | }) 222 | ``` 223 | 224 | 其中异步的`callback`函数,如果最后一个参数返回`true`则完成整个链接的调用,`false`则可以一直回调给原生,这个就是JavaScript端的一次调用,多次返回。比如需要将JavaScript端进度数据不间断同步到原生,这时就可以派上用场了。 225 | 226 | 227 | 3、通过`dsBridge`对象调用原生API,第一个参数是原生方法名称,第二参数是原生方法接收的参数,异步方法有第三个参数是回调函数,会接收`CompleteHandler`异步回调结果。 228 | 229 | ```typescript 230 | // 同步 231 | let msg = dsBridge.call('testSync', JSON.stringify({data: 100})) 232 | 233 | // 异步, 参数可以是对象或者基本类型 234 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 235 | updateMsg(msg) 236 | }) 237 | 238 | // 如果你使用的是DSBridge 2.0脚本,异步调用的参数必须是一个对象 239 | dsBridge.call('testAsync', {data: 200}, (msg) => { 240 | updateMsg(msg) 241 | }) 242 | ``` 243 | 244 | 245 | 246 | 247 | ## 进度回调(一次调用,多次返回) 248 | 249 | 前面提到了JavaScript端的一次调用,多次回调的情况,在原生端也是支持的,还是有应用场景的,比如将原生的下载进度实时同步到js中,可以通过`CompleteHandler#setProgressData()`方法来实现。 250 | 251 | ```typescript 252 | @JavaScriptInterface() 253 | testAsync(p: string, handler: CompleteHandler) { 254 | LogUtils.d("testAsync: " + JSON.stringify(p)) 255 | this.cHandler = handler 256 | let counter = 0 257 | setInterval(() => { 258 | if (counter < 5) { 259 | counter++ 260 | handler.setProgressData("异步返回的数据--" + counter) 261 | } else { 262 | this.cHandler.complete("异步返回的数据--结束") 263 | this.cHandler.complete("异步返回的数据--结束2") 264 | } 265 | }, 1000) 266 | ``` 267 | JavaScript: 268 | 269 | ```typescript 270 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 271 | updateMsg(msg) 272 | }) 273 | ``` 274 | 275 | ## 监听或拦截Javascript关闭页面 276 | 277 | Js调用`close()`函数可以关闭当前页面,原生可以设置监听观察是否拦截。 278 | 279 | ```typescript 280 | aboutToAppear() { 281 | this.controller.setClosePageListener(() => { 282 | return true; // false 会拦截关闭页面 283 | }) 284 | } 285 | ``` 286 | 287 | 在回调函数中如果返回`false`,会拦截掉关闭页面的事件。 288 | 289 | ## 销毁结束任务 290 | 291 | 如果异步任务还在执行中,比如`setProgressData`,此时关闭页面返回就会闪退,为了避免这种情形,建议在组件的生命周期函数`aboutToDisappear()`中结束任务。 292 | 293 | ```typescript 294 | aboutToDisappear(){ 295 | this.jsBridge.destroy() 296 | } 297 | 298 | ``` 299 | 300 | ## 命名空间 301 | 302 | 命名空间可以帮助你更好的管理API,这在API数量多的时候非常实用,支持你通过命名空间将API分类管理,不同级之间只需用'.' 分隔即可。支持同步与异步方式使用。 303 | 304 | 305 | ### ArkTS API命令空间 306 | 307 | 原生用`WebViewControllerProxy#addJavascriptObject` 指定一个命名空间名称: 308 | 309 | ```typescript 310 | this.controller.addJavascriptObject(new JsBridgeNamespace(), "namespace") 311 | ``` 312 | 313 | 314 | 在JavaScript中,用命名空间名称`.`对应的原生函数。 315 | 316 | ```javascript 317 | const callNative6 = () => { 318 | let msg = dsBridge.call('namespace.testSync',{msg:'来自js命名空间的数据'}) 319 | updateMsg(msg) 320 | } 321 | 322 | const callNative7 = () => { 323 | dsBridge.call('namespace.testAsync', 'test', (msg) => { 324 | updateMsg(msg) 325 | }) 326 | } 327 | 328 | ``` 329 | 330 | ### JavaScript API命令空间 331 | 332 | 用dsBridge对象注册js函数的命名空间。 333 | 334 | ```javascript 335 | // namespace 336 | dsBridge.register('sync', { 337 | test: function (a, b) { 338 | return "namespace: " + (a + b) 339 | } 340 | }) 341 | 342 | dsBridge.registerAsyn("asny",{ 343 | test: function (a,b ,callback) { 344 | callback("namespace: " + (a + b)) 345 | } 346 | }) 347 | ``` 348 | 349 | 第一个参数命名空间的名称,比如`sync`,第二个参数是API业务对象实例,支持字面量对象和Class类实例。 350 | 351 | 在原生调用方式: 352 | 353 | ```typescript 354 | 355 | this.controller.callJs("sync.test", [1, 2], (value: string) => { 356 | this.msg = value 357 | }) 358 | 359 | this.controller.callJs("asny.test", [3, 2], (value: string) => { 360 | this.msg = value 361 | }) 362 | ``` 363 | 364 | ## 原生同步方法内执行串行异步并发任务 365 | 366 | 原生同步方法: 367 | 368 | ```typescript 369 | /** 370 | * 同步模版 371 | * @param p 372 | * @returns 373 | */ 374 | @JavaScriptInterface(false) 375 | testSync(p: string): string { 376 | LogUtils.d("testSync: " + JSON.stringify(p)) 377 | return "原生同步testSync方法返回的数据" 378 | } 379 | ``` 380 | 381 | 如果要在同步方法内执行异步任务,并将异步结果立即返回给h5,上面的设计显然是无法满足需求的;在鸿蒙中异步任务基本与Promise和async/await有关联,然而桥接函数是不支持使用async/await声明(主要是受鸿蒙Web脚本注入机制的限制,也是为了考虑兼容Android/iOS项目而因此这样设计的)。 382 | 383 | 对此,设计了一个`taskWait()`函数来满足上述的需求,可以通过`taskWait()`函数在主线程的同步方法内执行串行并发异步任务,主线程会同步等待异步结果。 384 | 385 | ```typescript 386 | /** 387 | * 同步方法中执行异步并发任务 388 | * @param args 389 | * @returns 390 | */ 391 | @JavaScriptInterface(false) 392 | testTaskWait(args: string): number { 393 | let p = new Param(100, 200) 394 | let p1 = new Param(100, 300) 395 | taskWait(p) 396 | p1.tag = "Param" 397 | taskWait(p1) 398 | LogUtils.d(`testTaskWait sum: ${p.sum} ${p1.sum}`) 399 | return p.sum + p1.sum 400 | } 401 | 402 | ``` 403 | 404 | 其中`Param`类需要继承`BaseSendable`,同时用`@Sendable`装饰器声明,任务放在`run()`方法中执行。 405 | 406 | ```typescript 407 | @Sendable 408 | export class Param extends BaseSendable{ 409 | private a : number = 0 410 | private b : number = 0 411 | public sum: number = 0 412 | public enableLog: boolean = true 413 | 414 | constructor(a: number, b: number) { 415 | super(); 416 | this.a = a; 417 | this.b = b; 418 | } 419 | // 异步任务执行 420 | async run(): Promise { 421 | this.sum = await this.add() 422 | } 423 | 424 | async add(){ 425 | return this.a + this.b 426 | } 427 | 428 | 429 | } 430 | ``` 431 | 432 | `taskWait()`函数是一个轻量级的同步等待函数,不建议执行耗时过长的任务,如果在3s内没有完成任务,会自动结束等待将结果返回,可能会存在数据丢失的情况;对于特别耗时的任务建议使用异步桥接函数。 433 | 434 | ## 监听异常 435 | 436 | 可以通过setGlobalErrorMessageListener()方法来监听调用异常,如下: 437 | 438 | 439 | ```typescript 440 | this.controller.setGlobalErrorMessageListener((err: string) => { 441 | promptAction.showDialog({title:`${err}`}) 442 | }) 443 | 444 | ``` 445 | 446 | ## 交流 447 | 448 | 如有疑问,请提issues, 或加v进群交流:751496032,备注鸿蒙 449 | 450 | 451 | ## 最后 452 | 453 | 感谢[@name718](https://github.com/name718)、[@fjc0k](https://github.com/fjc0k)等各位大佬的支持与反馈。欢迎大家多多反馈,一起支持完善鸿蒙生态。 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | -------------------------------------------------------------------------------- /build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "signingConfigs": [ 4 | // { 5 | // "name": "default", 6 | // "type": "HarmonyOS", 7 | // "material": { 8 | // "certpath": "C:\\Users\\XML\\.ohos\\config\\auto_debug_dsbrige-demo_com.hzw.dsbrige_40086000000034244.cer", 9 | // "storePassword": "0000001B1C1534C7E1ABCEBA1CD52558F7E7779255409A648491F2D87B76D9A24466026C40941730300685", 10 | // "keyAlias": "debugKey", 11 | // "keyPassword": "0000001B4F1F3B61AB4D995FF1EE597FF674AC21FEDF291E39CD446C317255684ECD4EEEA1B449BFA1336E", 12 | // "profile": "C:\\Users\\XML\\.ohos\\config\\auto_debug_dsbrige-demo_com.hzw.dsbrige_40086000000034244.p7b", 13 | // "signAlg": "SHA256withECDSA", 14 | // "storeFile": "C:\\Users\\XML\\.ohos\\config\\auto_debug_dsbrige-demo_com.hzw.dsbrige_40086000000034244.p12" 15 | // } 16 | // }, 17 | // { 18 | // "name": "dev", 19 | // "type": "HarmonyOS", 20 | // "material": { 21 | // "storePassword": "0000001AD3E70B3390747316F31068ACB366520BEB3A38C0CC40BD3598C13D48CBD879A87CEE8C033F80", 22 | // "certpath": "./config/dsbridge.cer", 23 | // "keyAlias": "dsbridge", 24 | // "keyPassword": "0000001ADDD7BEAA0C1757391F0D72EAEFF37965BA2DFC12054DF5FDCFB17325D3559FA70B5E3C8FA801", 25 | // "profile": "./config/dsbridgeDebug.p7b", 26 | // "signAlg": "SHA256withECDSA", 27 | // "storeFile": "./config/dsbridge.p12" 28 | // } 29 | // } 30 | ], 31 | "products": [ 32 | { 33 | "name": "default", 34 | "signingConfig": "default", 35 | "compatibleSdkVersion": "5.0.0(12)", 36 | "targetSdkVersion": "5.0.0(12)", 37 | "runtimeOS": "HarmonyOS" 38 | } 39 | ] 40 | }, 41 | "modules": [ 42 | { 43 | "name": "entry", 44 | "srcPath": "./entry", 45 | "targets": [ 46 | { 47 | "name": "default", 48 | "applyToProducts": [ 49 | "default" 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | "name": "library", 56 | "srcPath": "./library" 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /entry/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /entry/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": 'stageMode', 3 | "buildOption": { 4 | }, 5 | "targets": [ 6 | { 7 | "name": "default" 8 | }, 9 | { 10 | "name": "ohosTest", 11 | } 12 | ], 13 | } -------------------------------------------------------------------------------- /entry/hvigorfile.ts: -------------------------------------------------------------------------------- 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'; 3 | -------------------------------------------------------------------------------- /entry/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": false 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "reflect-metadata@^0.1.13": "reflect-metadata@0.1.13", 9 | "@hzw/ohos-dsbridge@../library": "@hzw/ohos-dsbridge@../library" 10 | }, 11 | "packages": { 12 | "@hzw/ohos-dsbridge@../library": { 13 | "name": "@hzw/ohos-dsbridge", 14 | "version": "1.7.2", 15 | "resolved": "../library", 16 | "registryType": "local", 17 | "dependencies": { 18 | "reflect-metadata": "^0.1.13" 19 | } 20 | }, 21 | "reflect-metadata@0.1.13": { 22 | "name": "reflect-metadata", 23 | "version": "0.1.13", 24 | "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", 25 | "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.1.13.tgz", 26 | "shasum": "67ae3ca57c972a2aa1642b10fe363fe32d49dc08", 27 | "registryType": "ohpm" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /entry/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "", 3 | "devDependencies": {}, 4 | "author": "HZWei", 5 | "name": "entry", 6 | "description": "Please describe the basic information.", 7 | "main": "", 8 | "version": "1.0.0", 9 | "dependencies": { 10 | "reflect-metadata": "^0.1.13", 11 | "@hzw/ohos-dsbridge": "file:../library" 12 | }, 13 | "dynamicDependencies": {} 14 | } -------------------------------------------------------------------------------- /entry/src/main/ets/bridge/JsBridge.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils'; 2 | import { BaseSendable, CompleteHandler, JavaScriptInterface, taskWait } from '@hzw/ohos-dsbridge'; 3 | import { ToastUtils } from '@hzw/ohos-dsbridge/src/main/ets/utils/ToastUtils'; 4 | 5 | @Sendable 6 | export class Param extends BaseSendable{ 7 | private a : number = 0 8 | private b : number = 0 9 | public sum: number = 0 10 | public enableLog: boolean = true 11 | 12 | constructor(a: number, b: number) { 13 | super(); 14 | this.a = a; 15 | this.b = b; 16 | } 17 | 18 | async run(): Promise { 19 | // let currentTime = Date.now() 20 | // while (true) { 21 | // if (Date.now() - currentTime > 5000) { 22 | // break 23 | // } 24 | // } 25 | // throw new Error("ddd") 26 | // this.sum = await this.add() 27 | // let target = this 28 | // this.sum = await new Promise(async (resolve, reject) => { 29 | // let sum = this.add() 30 | // resolve(sum) 31 | // }) 32 | this.sum = await this.addTest() 33 | 34 | } 35 | 36 | async addTest() :Promise{ 37 | return new Promise(async (resolve, reject) => { 38 | let sum = this.add() 39 | resolve(sum) 40 | }) 41 | } 42 | 43 | async add(){ 44 | return this.a + this.b 45 | } 46 | 47 | 48 | } 49 | 50 | export class JsBridge { 51 | private cHandler?: CompleteHandler 52 | 53 | /** 54 | * 同步模版 55 | * @param p 56 | * @returns 57 | */ 58 | @JavaScriptInterface(false) 59 | testSync(p: string): string { 60 | LogUtils.d("testSync: " + JSON.stringify(p)) 61 | return "原生同步testSync方法返回的数据" 62 | } 63 | 64 | /** 65 | * 同步方法中执行异步并发任务 66 | * @param args 67 | * @returns 68 | */ 69 | @JavaScriptInterface(false) 70 | testTaskWait(args: string): number { 71 | let p = new Param(100, 200) 72 | let p1 = new Param(100, 300) 73 | taskWait(p) 74 | p1.tag = "Param" 75 | taskWait(p1) 76 | LogUtils.d(`testTaskWait sum: ${p.sum} ${p1.sum}`) 77 | return p.sum + p1.sum 78 | } 79 | 80 | @JavaScriptInterface(false) 81 | testNoArg(): boolean { 82 | LogUtils.d('testNoArg') 83 | return true 84 | } 85 | 86 | @JavaScriptInterface() 87 | testNoArgAsync() { 88 | LogUtils.d('testNoArgAsync') 89 | ToastUtils.show("testNoArgAsync") 90 | } 91 | 92 | /** 93 | * 异步模版 94 | * @param args 95 | * @param handler 96 | */ 97 | @JavaScriptInterface() 98 | testAsync(args: string, handler: CompleteHandler) { 99 | LogUtils.d("testAsync: " + JSON.stringify(args)) 100 | this.cHandler = handler 101 | this.countdown(5, (time:number) => { 102 | if (time === 0) { 103 | handler.complete("原生异步testAsync方法返回的数据--结束") 104 | handler.complete("原生异步testAsync方法返回的数据--结束2") // 不会被调用 会报错 Uncaught ReferenceError: xxx is not defined 105 | } else { 106 | handler.setProgressData("原生异步testAsync方法返回的数据--" + time) 107 | } 108 | }) 109 | 110 | 111 | } 112 | 113 | countdown(seconds: number, callback: (counter: number) => void) { 114 | let count = seconds; 115 | const interval = setInterval(() => { 116 | if (count === 0) { 117 | clearInterval(interval); 118 | LogUtils.d("Finished"); 119 | return; 120 | } 121 | count-- 122 | callback(count) 123 | }, 1000); 124 | } 125 | } 126 | 127 | -------------------------------------------------------------------------------- /entry/src/main/ets/bridge/JsBridge2.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils'; 2 | import { BaseSendable, CompleteHandler, JavaScriptInterface, taskWait } from '@hzw/ohos-dsbridge'; 3 | import { JsParam } from '../pages/UseInJs2Page'; 4 | 5 | /** 6 | * @author: HZWei 7 | * @date: 2024/7/6 8 | * @desc: 测试DSBridge2.0脚本 9 | */ 10 | 11 | export class JsBridge2 { 12 | 13 | @JavaScriptInterface(false) 14 | testComponentSync(args: string): Object { 15 | const jsParam = JSON.parse(args) as JsParam 16 | LogUtils.d(jsParam.msg ?? '') 17 | // return `组件中的同步方法: ${jsParam.msg}` 18 | return `${jsParam.msg}` 19 | } 20 | 21 | @JavaScriptInterface(false) 22 | testNoArgSync(): number { 23 | return 1 + 2 24 | } 25 | 26 | @JavaScriptInterface() 27 | testComponentAsync(args: string, handler: CompleteHandler) { 28 | // LogUtils.d(args) 29 | const jsParam = JSON.parse(args) as JsParam 30 | handler.complete(`组件中的异步方法: ${jsParam.msg}`) 31 | } 32 | 33 | @JavaScriptInterface() 34 | testCallNoArgAsync(handler: CompleteHandler) { 35 | handler.complete(`无参异步方法返回值: 100`) 36 | } 37 | 38 | @JavaScriptInterface() 39 | testProgressAsync(handler: CompleteHandler) { 40 | this.countdown(5, (time: number) => { 41 | if (time === 0) { 42 | handler.complete("原生异步方法返回的数据--结束") 43 | handler.complete("原生异步方法返回的数据--结束2") // 不会被调用 会报错 Uncaught ReferenceError: xxx is not defined 44 | } else { 45 | handler.setProgressData("原生异步方法返回的数据--" + time) 46 | } 47 | }) 48 | } 49 | 50 | countdown(seconds: number, callback: (counter: number) => void) { 51 | let count = seconds; 52 | const interval = setInterval(() => { 53 | if (count === 0) { 54 | clearInterval(interval); 55 | LogUtils.d("Finished"); 56 | return; 57 | } 58 | count-- 59 | callback(count) 60 | }, 1000); 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /entry/src/main/ets/bridge/JsBridgeNamespace.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils'; 2 | import { CompleteHandler, JavaScriptInterface } from '@hzw/ohos-dsbridge'; 3 | 4 | export class JsBridgeNamespace { 5 | private cHandler?: CompleteHandler 6 | 7 | /** 8 | * 同步模版 9 | * @param p 10 | * @returns 11 | */ 12 | @JavaScriptInterface(false) 13 | testSync(p: string): string { 14 | LogUtils.d("namespace testSync: " + JSON.stringify(p)) 15 | return "namespace: 原生同步testSync方法返回的数据" 16 | } 17 | /** 18 | * 异步模版 19 | * @param p 20 | * @param handler 21 | */ 22 | @JavaScriptInterface() 23 | testAsync(p: string, handler: CompleteHandler) { 24 | LogUtils.d("namespace testAsync: " + JSON.stringify(p)) 25 | this.cHandler = handler 26 | this.countdown(5, (time:number) => { 27 | if (time === 0) { 28 | handler.complete("namespace: 原生异步testAsync方法返回的数据--结束") 29 | handler.complete("namespace: 原生异步testAsync方法返回的数据--结束2") // 不会被调用 会报错 Uncaught ReferenceError: xxx is not defined 30 | } else { 31 | handler.setProgressData("namespace: 原生异步testAsync方法返回的数据--" + time) 32 | } 33 | }) 34 | 35 | 36 | } 37 | 38 | countdown(seconds: number, callback: (counter: number) => void) { 39 | let count = seconds; 40 | const interval = setInterval(() => { 41 | if (count === 0) { 42 | clearInterval(interval); 43 | LogUtils.d("Finished"); 44 | return; 45 | } 46 | count-- 47 | callback(count) 48 | }, 1000); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /entry/src/main/ets/entryability/EntryAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import hilog from '@ohos.hilog'; 3 | import window from '@ohos.window'; 4 | import { AbilityConstant, Want } from '@kit.AbilityKit'; 5 | 6 | export default class EntryAbility extends UIAbility { 7 | onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { 8 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); 9 | } 10 | 11 | onDestroy() { 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); 13 | } 14 | 15 | onWindowStageCreate(windowStage: window.WindowStage) { 16 | // Main window is created, set main page for this ability 17 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); 18 | 19 | windowStage.loadContent('pages/Index', (err, data) => { 20 | if (err.code) { 21 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 22 | return; 23 | } 24 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); 25 | }); 26 | } 27 | 28 | onWindowStageDestroy() { 29 | // Main window is destroyed, release UI related resources 30 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); 31 | } 32 | 33 | onForeground() { 34 | // Ability has brought to foreground 35 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); 36 | } 37 | 38 | onBackground() { 39 | // Ability has back to background 40 | hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import router from '@ohos.router' 2 | @Entry 3 | @Component 4 | struct Index { 5 | @State message: string = 'Hello World' 6 | 7 | build() { 8 | Row() { 9 | Column() { 10 | Button('NativeAndJsCalls - DSBridge3.0') 11 | .onClick(() => { 12 | router.pushUrl({ url: "pages/NativeAndJsCallsPage" }) 13 | }) 14 | 15 | Button('UseInComponent - DSBridge3.0') 16 | .margin('10vp') 17 | .onClick(() => { 18 | router.pushUrl({ url: "pages/UseInComponentsPage" }) 19 | }) 20 | 21 | Button('UseInJs2 - 适配DSBridge2.0脚本') 22 | .margin('10vp') 23 | .onClick(() => { 24 | router.pushUrl({ url: "pages/UseInJs2Page" }) 25 | }) 26 | 27 | Button("加载h5").onClick((event: ClickEvent) => { 28 | router.pushUrl({ url: "pages/TestPage" }) 29 | }) 30 | } 31 | .width('100%') 32 | } 33 | .height('100%') 34 | } 35 | } -------------------------------------------------------------------------------- /entry/src/main/ets/pages/NativeAndJsCallsPage.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils' 2 | 3 | import { JavaScriptInterface, WebViewControllerProxy } from '@hzw/ohos-dsbridge' 4 | import { JsBridge } from '../bridge/JsBridge' 5 | import { JsBridgeNamespace } from '../bridge/JsBridgeNamespace' 6 | import { promptAction } from '@kit.ArkUI' 7 | 8 | @Component 9 | @Entry 10 | struct NativeAndJsCallsPage { 11 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 12 | private url: string = "https://mp.weixin.qq.com/s/kAtQ98mhdbYheo-SIIaFOQ" 13 | private localPath = $rawfile('index.html') 14 | @State isLoading: boolean = true 15 | @State msg: string = "" 16 | 17 | aboutToAppear() { 18 | this.controller.addJavascriptObject(new JsBridge()) 19 | this.controller.addJavascriptObject(new JsBridgeNamespace(), "namespace") 20 | // this.controller.addJavascriptObject(this) 21 | this.controller.setClosePageListener(() => { 22 | return true; // false 会拦截关闭页面 23 | }) 24 | this.controller.setGlobalErrorMessageListener((err: string) => { 25 | promptAction.showDialog({title:`err: ${err}`}) 26 | }) 27 | } 28 | 29 | @JavaScriptInterface(false) 30 | testNoArg(): boolean { 31 | LogUtils.d('testNoArg') 32 | return true 33 | } 34 | 35 | 36 | aboutToDisappear() { 37 | this.controller.destroy() 38 | } 39 | 40 | build() { 41 | // 注意需要请求网络权限 42 | Column() { 43 | Stack() { 44 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 45 | .javaScriptAccess(true) 46 | .javaScriptProxy(this.controller.javaScriptProxy) 47 | .onAlert((event) => { 48 | // AlertDialog.show({ message: event.message }) 49 | return false 50 | }) 51 | .onProgressChange((event) => { 52 | if (event?.newProgress == 100) { 53 | this.isLoading = false 54 | } 55 | }) 56 | .onConsole((event) => { 57 | LogUtils.d('getMessage:' + event?.message.getMessage()); 58 | return false; 59 | }) 60 | .width('100%') 61 | .height('100%') 62 | .backgroundColor("#ffeef5ee") 63 | 64 | if (this.isLoading) { 65 | LoadingProgress() 66 | .width(40) 67 | .height(40) 68 | } 69 | 70 | } 71 | .alignContent(Alignment.Center) 72 | .width('100%') 73 | .height('70%') 74 | 75 | Column() { 76 | Text(this.msg ? "js返回的数据: " + this.msg : "") 77 | .padding(20) 78 | Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) { 79 | Button("调用js函数-同步") 80 | .margin({ top: 10 }) 81 | .onClick(() => { 82 | this.controller.callJs("showAlert", [1, 2, '666'], (v: string) => { 83 | this.msg = v + "" 84 | }) 85 | }) 86 | 87 | Button("调用js函数-异步") 88 | .margin({ top: 10 }) 89 | .onClick(() => { 90 | this.controller.callJs("showAlertAsync", [1, 2, '666'], (v: string) => { 91 | this.msg = v + "" 92 | }) 93 | }) 94 | 95 | Button("检测js注册的函数1") 96 | .margin({ top: 10 }) 97 | .onClick(async () => { 98 | let has = await this.controller.hasJavascriptMethod("showAlert") 99 | this.msg = `showAlert: ${has}` 100 | }) 101 | Button("检测js注册的函数2") 102 | .margin({ top: 10 }) 103 | .onClick(async () => { 104 | let has = await this.controller.hasJavascriptMethod("showAlertAy") 105 | this.msg = `showAlertAy: ${has}` 106 | }) 107 | 108 | Button("命名空间:调用js函数-同步") 109 | .margin({ top: 10 }) 110 | .onClick(async () => { 111 | this.controller.callJs("sync.test", [1, 2], (value: string) => { 112 | this.msg = value 113 | }) 114 | }) 115 | 116 | Button("命名空间:调用js函数-异步") 117 | .margin({ top: 10 }) 118 | .onClick(async () => { 119 | this.controller.callJs("asny.test", [3, 2], (value: string) => { 120 | this.msg = value 121 | }) 122 | }) 123 | 124 | Button("backward").onClick((event: ClickEvent) => { 125 | this.controller.getWebViewController().backward() 126 | }) 127 | 128 | } 129 | .width('100%') 130 | } 131 | .width('100%') 132 | .alignItems(HorizontalAlign.Center) 133 | 134 | } 135 | .width('100%') 136 | .height('100%') 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/TestPage.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils' 2 | 3 | import { CompleteHandler, JavaScriptInterface, WebViewControllerProxy } from '@hzw/ohos-dsbridge' 4 | import { JsBridge } from '../bridge/JsBridge' 5 | import { webview } from '@kit.ArkWeb' 6 | import { promptAction, router } from '@kit.ArkUI' 7 | import { JsBridge2 } from '../bridge/JsBridge2' 8 | 9 | /** 10 | * @author: HZWei 11 | * @date: 2024/11/20 12 | * @desc: 13 | */ 14 | @Component 15 | @Entry 16 | struct TestPage { 17 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 18 | private url: string = "https://www.harmonyos.com/" 19 | @State isLoading: boolean = true 20 | @State msg: string = "" 21 | 22 | aboutToAppear() { 23 | 24 | // 开启调试模式 25 | webview.WebviewController.setWebDebuggingAccess(true); 26 | } 27 | 28 | 29 | 30 | 31 | aboutToDisappear() { 32 | this.controller.destroy() 33 | } 34 | 35 | build() { 36 | Column({space:20}) { 37 | Stack() { 38 | Web({ src: this.url, controller: this.controller.getWebViewController() }) 39 | .javaScriptAccess(true) 40 | .javaScriptProxy(this.controller.javaScriptProxy) 41 | .onAlert((event) => { 42 | return false 43 | }) 44 | .onProgressChange((event) => { 45 | if (event?.newProgress == 100) { 46 | this.isLoading = false 47 | } 48 | }) 49 | .onConsole((event) => { 50 | LogUtils.d('getMessage:' + event?.message.getMessage()); 51 | return false; 52 | }) 53 | .width('100%') 54 | .height('100%') 55 | .backgroundColor("#ffeef5ee") 56 | 57 | if (this.isLoading) { 58 | LoadingProgress() 59 | .width(40) 60 | .height(40) 61 | } 62 | 63 | } 64 | .alignContent(Alignment.Center) 65 | .width('100%') 66 | .height('70%') 67 | 68 | Text(this.msg) 69 | .fontSize(18) 70 | .padding(10) 71 | 72 | Button("back").onClick((event: ClickEvent) => { 73 | if (this.controller.getWebViewController().accessBackward()){ 74 | this.controller.getWebViewController().backward() 75 | }else { 76 | router.back() 77 | } 78 | }) 79 | 80 | 81 | 82 | } 83 | .width('100%') 84 | .height('100%') 85 | } 86 | } 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/UseInComponentsPage.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils' 2 | 3 | import { CompleteHandler, JavaScriptInterface, WebViewControllerProxy } from '@hzw/ohos-dsbridge' 4 | import { JsBridge } from '../bridge/JsBridge' 5 | import { webview } from '@kit.ArkWeb' 6 | import { promptAction } from '@kit.ArkUI' 7 | 8 | /** 9 | * @author: HZWei 10 | * @date: 2024/6/24 11 | * @desc: 在组件中定义桥接函数示例 12 | */ 13 | 14 | @Component 15 | @Entry 16 | struct UseInComponentsPage { 17 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 18 | private url: string = "https://www.harmonyos.com/" 19 | private localPath = $rawfile('use-in-components.html') 20 | @State isLoading: boolean = true 21 | @State msg: string = "" 22 | 23 | aboutToAppear() { 24 | // 在一个组件内只能存在一个无命名空间类 25 | this.controller.addJavascriptObject(this) 26 | this.controller.setGlobalErrorMessageListener((err: string) => { 27 | promptAction.showDialog({title:`${err}`}) 28 | }) 29 | 30 | 31 | // 开启调试模式 32 | webview.WebviewController.setWebDebuggingAccess(true); 33 | } 34 | 35 | @JavaScriptInterface(false) 36 | testComponentSync(args: string): string { 37 | return `组件中的同步方法: ${args}` 38 | } 39 | 40 | @JavaScriptInterface() 41 | testComponentAsync(args: string, handler: CompleteHandler) { 42 | handler.complete(`组件中的异步方法: ${args}`) 43 | } 44 | 45 | aboutToDisappear() { 46 | this.controller.destroy() 47 | } 48 | 49 | build() { 50 | Column() { 51 | Stack() { 52 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 53 | .javaScriptAccess(true) 54 | .javaScriptProxy(this.controller.javaScriptProxy) 55 | .onAlert((event) => { 56 | return false 57 | }) 58 | .onProgressChange((event) => { 59 | if (event?.newProgress == 100) { 60 | this.isLoading = false 61 | } 62 | }) 63 | .onConsole((event) => { 64 | LogUtils.d('getMessage:' + event?.message.getMessage()); 65 | return false; 66 | }) 67 | .width('100%') 68 | .height('100%') 69 | .backgroundColor("#ffeef5ee") 70 | 71 | if (this.isLoading) { 72 | LoadingProgress() 73 | .width(40) 74 | .height(40) 75 | } 76 | 77 | } 78 | .alignContent(Alignment.Center) 79 | .width('100%') 80 | .height('70%') 81 | 82 | } 83 | .width('100%') 84 | .height('100%') 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /entry/src/main/ets/pages/UseInJs2Page.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils' 2 | 3 | import { CompleteHandler, JavaScriptInterface, WebViewControllerProxy } from '@hzw/ohos-dsbridge' 4 | import { JsBridge } from '../bridge/JsBridge' 5 | import { webview } from '@kit.ArkWeb' 6 | import { promptAction } from '@kit.ArkUI' 7 | import { JsBridge2 } from '../bridge/JsBridge2' 8 | 9 | /** 10 | * @author: HZWei 11 | * @date: 2024/7/6 12 | * @desc: 测试dsBridge2的js 13 | */ 14 | 15 | @Component 16 | @Entry 17 | struct UseInJs2Page { 18 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 19 | private url: string = "https://www.harmonyos.com/" 20 | private localPath = $rawfile('use-in-js2.html') 21 | @State isLoading: boolean = true 22 | @State msg: string = "" 23 | 24 | aboutToAppear() { 25 | // 如果是使用DSBridge2.0 ,调用supportDS2方法 26 | this.controller.supportDS2(true) 27 | // 以下两种注册方式都可以,任选其一 28 | this.controller.addJavascriptObject(this) 29 | // this.controller.addJavascriptObject(new JsBridge2()) 30 | 31 | 32 | // DS2.0脚本不支持API命令空间 xx 33 | // this.controller.addJavascriptObject(new JsBridge2(),'js2') 34 | // 开启调试模式 35 | webview.WebviewController.setWebDebuggingAccess(true); 36 | 37 | this.controller.setGlobalErrorMessageListener((err: string) => { 38 | promptAction.showDialog({title:`${err}`}) 39 | }) 40 | 41 | } 42 | 43 | @JavaScriptInterface(false) 44 | testComponentSync(args: string): Object { 45 | const jsParam = JSON.parse(args) as JsParam 46 | LogUtils.d(jsParam.msg ?? '') 47 | // return `组件中的同步方法: ${jsParam.msg}` 48 | return `${jsParam.msg}` 49 | } 50 | 51 | @JavaScriptInterface(false) 52 | testNoArgSync(): number { 53 | return 1 + 2 54 | } 55 | 56 | @JavaScriptInterface() 57 | testComponentAsync(args: string, handler: CompleteHandler) { 58 | // LogUtils.d(args) 59 | const jsParam = JSON.parse(args) as JsParam 60 | handler.complete(`组件中的异步方法: ${jsParam.msg}`) 61 | } 62 | 63 | @JavaScriptInterface() 64 | testCallNoArgAsync(handler: CompleteHandler) { 65 | handler.complete(`无参异步方法返回值: 100`) 66 | } 67 | 68 | @JavaScriptInterface() 69 | testProgressAsync(handler: CompleteHandler) { 70 | this.countdown(5, (time: number) => { 71 | if (time === 0) { 72 | handler.complete("原生异步方法返回的数据--结束") 73 | handler.complete("原生异步方法返回的数据--结束2") // 不会被调用 会报错 Uncaught ReferenceError: xxx is not defined 74 | } else { 75 | handler.setProgressData("原生异步方法返回的数据--" + time) 76 | } 77 | }) 78 | } 79 | 80 | countdown(seconds: number, callback: (counter: number) => void) { 81 | let count = seconds; 82 | const interval = setInterval(() => { 83 | if (count === 0) { 84 | clearInterval(interval); 85 | LogUtils.d("Finished"); 86 | return; 87 | } 88 | count-- 89 | callback(count) 90 | }, 1000); 91 | } 92 | 93 | aboutToDisappear() { 94 | this.controller.destroy() 95 | } 96 | 97 | build() { 98 | Column() { 99 | Stack() { 100 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 101 | .javaScriptAccess(true) 102 | .javaScriptProxy(this.controller.javaScriptProxy) 103 | .onAlert((event) => { 104 | return false 105 | }) 106 | .onProgressChange((event) => { 107 | if (event?.newProgress == 100) { 108 | this.isLoading = false 109 | } 110 | }) 111 | .onConsole((event) => { 112 | LogUtils.d('getMessage:' + event?.message.getMessage()); 113 | return false; 114 | }) 115 | .width('100%') 116 | .height('100%') 117 | .backgroundColor("#ffeef5ee") 118 | 119 | if (this.isLoading) { 120 | LoadingProgress() 121 | .width(40) 122 | .height(40) 123 | } 124 | 125 | } 126 | .alignContent(Alignment.Center) 127 | .width('100%') 128 | .height('70%') 129 | 130 | Text(this.msg) 131 | .fontSize(18) 132 | .padding(10) 133 | 134 | Button("callJs").onClick((event: ClickEvent) => { 135 | this.controller.callHandler('addValue', [1, 2], (r: number) => { 136 | this.msg = r.toString() 137 | }) 138 | }) 139 | 140 | Button("callJs2").onClick((event: ClickEvent) => { 141 | this.controller.callHandlerNoParam('addValue2', () => { 142 | this.msg = 'addValue2' 143 | }) 144 | }) 145 | 146 | } 147 | .width('100%') 148 | .height('100%') 149 | } 150 | } 151 | 152 | export class JsParam { 153 | msg?: string 154 | } 155 | 156 | -------------------------------------------------------------------------------- /entry/src/main/ets/utils/LogUtils.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog' 2 | 3 | export class LogUtils{ 4 | private static readonly LOG_TAG: string = "LogUtils" 5 | 6 | static d(msg: string) { 7 | hilog.debug(0x0001, LogUtils.LOG_TAG, msg) 8 | } 9 | 10 | static e(msg: string) { 11 | hilog.error(0x0001, LogUtils.LOG_TAG, msg) 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /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 | ], 10 | "deliveryWithInstall": true, 11 | "installationFree": false, 12 | "pages": "$profile:main_pages", 13 | "requestPermissions": [{ 14 | "name": "ohos.permission.INTERNET", 15 | }], 16 | "abilities": [ 17 | { 18 | "name": "EntryAbility", 19 | "srcEntry": "./ets/entryability/EntryAbility.ets", 20 | "description": "$string:EntryAbility_desc", 21 | "icon": "$media:icon", 22 | "label": "$string:EntryAbility_label", 23 | "startWindowIcon": "$media:icon", 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 | } 39 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /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": "dsbrige-demo" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/main/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/751496032/DSBridge-HarmonyOS/7515ee4037c91959c4a7e2244802cf67a3e3a7c3/entry/src/main/resources/base/media/icon.png -------------------------------------------------------------------------------- /entry/src/main/resources/base/profile/main_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "pages/Index", 4 | "pages/NativeAndJsCallsPage", 5 | "pages/UseInComponentsPage", 6 | "pages/UseInJs2Page", 7 | "pages/TestPage" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/dsBridge3.0.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const bridge = { 3 | call: function (method, args, callback) { 4 | let params = {data: args === undefined ? null : args} 5 | if (callback != null && typeof callback == 'function') { 6 | if (!window.callID) { 7 | window.callID = 0 8 | } 9 | const callName = "dscall" + (window.callID++) 10 | window[callName] = callback 11 | params["_dscbstub"] = callName 12 | } 13 | let paramsStr = JSON.stringify(params) 14 | let res = "" 15 | if (window._dsbridge){ 16 | res = window._dsbridge.call(method, paramsStr) 17 | } 18 | return JSON.parse(res).data 19 | }, 20 | register: function (method, func, async) { 21 | if (window._dsaf && window._dsf){ 22 | let obj = async ? window._dsaf : window._dsf 23 | obj[method] = func 24 | if (typeof func == "object") { 25 | obj._obs[method] = func; 26 | } else { 27 | obj[method] = func; 28 | } 29 | } 30 | 31 | }, 32 | registerAsyn: function (method, func) { 33 | this.register(method, func, true) 34 | }, 35 | registerAsync: function (method, func) { 36 | this.register(method, func, true) 37 | }, 38 | hasNativeMethod: function (method) { 39 | return this.call("_dsb.hasNativeMethod", {name: method}) 40 | }, 41 | close: function () { 42 | this.call("_dsb.closePage") 43 | }, 44 | }; 45 | 46 | (function (){ 47 | const manager = { 48 | _dsf: { 49 | _obs: {} 50 | }, 51 | _dsaf: { 52 | _obs: {} 53 | }, 54 | dsBridge: bridge, 55 | close: function () { 56 | bridge.close() 57 | }, 58 | _handleMessageFromNative: function (info) { 59 | 60 | let arg = JSON.parse(info.data); 61 | let ret = { 62 | id: info.callbackId, complete: true 63 | } 64 | let f = this._dsf[info.method]; 65 | let af = this._dsaf[info.method] 66 | let callSyn = function (f, ob) { 67 | ret.data = f.apply(ob, arg) 68 | bridge.call("_dsb.returnValue", ret) 69 | } 70 | let callAsync = function (f, ob) { 71 | arg.push(function (data, complete) { 72 | ret.data = data; 73 | ret.complete = complete !== false; 74 | bridge.call("_dsb.returnValue", ret) 75 | }) 76 | f.apply(ob, arg) 77 | } 78 | if (f) { 79 | callSyn(f, this._dsf); 80 | } else if (af) { 81 | callAsync(af, this._dsaf); 82 | }else { 83 | // namespace 84 | let name = info.method.split('.'); 85 | if (name.length<2) return; 86 | let method=name.pop(); 87 | let namespace=name.join('.') 88 | let obs = this._dsf._obs; 89 | let ob = obs[namespace] || {}; 90 | let m = ob[method]; 91 | if (m && typeof m == "function") { 92 | callSyn(m, ob); 93 | return; 94 | } 95 | obs = this._dsaf._obs; 96 | ob = obs[namespace] || {}; 97 | m = ob[method]; 98 | if (m && typeof m == "function") { 99 | callAsync(m, ob); 100 | return; 101 | } 102 | } 103 | } 104 | } 105 | 106 | for (let attr in manager) { 107 | window[attr] = manager[attr] 108 | console.log(attr) 109 | } 110 | 111 | dsBridge.register("_hasJavascriptMethod", (method) => { 112 | const name = method.split('.') 113 | if (name.length < 2) { 114 | return !!(_dsf[name] || _dsaf[name]) 115 | } else { 116 | // namespace 117 | let method = name.pop() 118 | let namespace = name.join('.') 119 | let ob = _dsf._obs[namespace] || _dsaf._obs[namespace] 120 | return ob && !!ob[method] 121 | } 122 | }) 123 | 124 | })(); 125 | 126 | // module.exports = dsBridge; 127 | // export default dsBridge; 128 | 129 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/dsbridge2.0.js: -------------------------------------------------------------------------------- 1 | function getJsBridge() { 2 | window._dsf = window._dsf || {}; 3 | return { 4 | call: function (method, args, cb) { 5 | var ret = ""; 6 | if (typeof args == "function") { 7 | cb = args; 8 | args = {} 9 | } 10 | if (typeof cb == "function") { 11 | window.dscb = window.dscb || 0; 12 | var cbName = "dscb" + window.dscb++; 13 | window[cbName] = cb; 14 | args["_dscbstub"] = cbName 15 | } 16 | console.log('cb: ',JSON.stringify(args) , typeof cb == "function"); 17 | args = JSON.stringify(args || {}); 18 | if (window._dswk) { 19 | 20 | ret = prompt(window._dswk + method, args) 21 | } else { 22 | if (typeof _dsbridge == "function") { 23 | ret = _dsbridge(method, args) 24 | } else { 25 | ret = _dsbridge.call(method, args) 26 | } 27 | } 28 | return ret 29 | }, register: function (name, fun) { 30 | if (typeof name == "object") { 31 | Object.assign(window._dsf, name) 32 | } else { 33 | window._dsf[name] = fun 34 | } 35 | } 36 | } 37 | } 38 | 39 | window.dsBridge = getJsBridge(); 40 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 50 | 51 | 52 | 53 |
54 |

55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 | 71 | 214 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/new-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 50 | 51 | 52 | 53 |
54 |

新页面

55 |

56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 | 191 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/use-in-components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 50 | 51 | 52 | 53 |
54 |

55 |
56 |
57 | 58 | 59 |
60 | 61 | 80 | -------------------------------------------------------------------------------- /entry/src/main/resources/rawfile/use-in-js2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 48 | 49 | 50 | 51 |
52 |

53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | 63 | 119 | -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/test/Ability.test.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' 3 | 4 | export default function abilityTest() { 5 | describe('ActsAbilityTest', function () { 6 | // Defines a test suite. Two parameters are supported: test suite name and test suite function. 7 | beforeAll(function () { 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(function () { 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(function () { 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(function () { 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, function () { 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/ohosTest/ets/test/List.test.ets: -------------------------------------------------------------------------------- 1 | import abilityTest from './Ability.test' 2 | 3 | export default function testsuite() { 4 | abilityTest() 5 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testability/TestAbility.ets: -------------------------------------------------------------------------------- 1 | import UIAbility from '@ohos.app.ability.UIAbility'; 2 | import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; 3 | import hilog from '@ohos.hilog'; 4 | import { Hypium } from '@ohos/hypium'; 5 | import testsuite from '../test/List.test'; 6 | import window from '@ohos.window'; 7 | 8 | export default class TestAbility extends UIAbility { 9 | onCreate(want, launchParam) { 10 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); 11 | hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); 12 | hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); 13 | var abilityDelegator: any 14 | abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() 15 | var abilityDelegatorArguments: any 16 | abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() 17 | hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); 18 | Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) 19 | } 20 | 21 | onDestroy() { 22 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); 23 | } 24 | 25 | onWindowStageCreate(windowStage: window.WindowStage) { 26 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); 27 | windowStage.loadContent('testability/pages/Index', (err, data) => { 28 | if (err.code) { 29 | hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); 30 | return; 31 | } 32 | hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', 33 | JSON.stringify(data) ?? ''); 34 | }); 35 | } 36 | 37 | onWindowStageDestroy() { 38 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); 39 | } 40 | 41 | onForeground() { 42 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); 43 | } 44 | 45 | onBackground() { 46 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); 47 | } 48 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testability/pages/Index.ets: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | 3 | @Entry 4 | @Component 5 | struct Index { 6 | aboutToAppear() { 7 | hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); 8 | } 9 | @State message: string = 'Hello World' 10 | build() { 11 | Row() { 12 | Column() { 13 | Text(this.message) 14 | .fontSize(50) 15 | .fontWeight(FontWeight.Bold) 16 | Button() { 17 | Text('next page') 18 | .fontSize(20) 19 | .fontWeight(FontWeight.Bold) 20 | }.type(ButtonType.Capsule) 21 | .margin({ 22 | top: 20 23 | }) 24 | .backgroundColor('#0D9FFB') 25 | .width('35%') 26 | .height('5%') 27 | .onClick(()=>{ 28 | }) 29 | } 30 | .width('100%') 31 | } 32 | .height('100%') 33 | } 34 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog'; 2 | import TestRunner from '@ohos.application.testRunner'; 3 | import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; 4 | 5 | var abilityDelegator = undefined 6 | var abilityDelegatorArguments = undefined 7 | 8 | async function onAbilityCreateCallback() { 9 | hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); 10 | } 11 | 12 | async function addAbilityMonitorCallback(err: any) { 13 | hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); 14 | } 15 | 16 | export default class OpenHarmonyTestRunner implements TestRunner { 17 | constructor() { 18 | } 19 | 20 | onPrepare() { 21 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); 22 | } 23 | 24 | async onRun() { 25 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); 26 | abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() 27 | abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() 28 | var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' 29 | let lMonitor = { 30 | abilityName: testAbilityName, 31 | onAbilityCreate: onAbilityCreateCallback, 32 | }; 33 | abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) 34 | var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName 35 | var debug = abilityDelegatorArguments.parameters['-D'] 36 | if (debug == 'true') 37 | { 38 | cmd += ' -D' 39 | } 40 | hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); 41 | abilityDelegator.executeShellCommand(cmd, 42 | (err: any, d: any) => { 43 | hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); 44 | hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); 45 | hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); 46 | }) 47 | hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); 48 | } 49 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "entry_test", 4 | "type": "feature", 5 | "description": "$string:module_test_desc", 6 | "mainElement": "TestAbility", 7 | "deviceTypes": [ 8 | "phone" 9 | ], 10 | "deliveryWithInstall": true, 11 | "installationFree": false, 12 | "pages": "$profile:test_pages", 13 | "abilities": [ 14 | { 15 | "name": "TestAbility", 16 | "srcEntry": "./ets/testability/TestAbility.ets", 17 | "description": "$string:TestAbility_desc", 18 | "icon": "$media:icon", 19 | "label": "$string:TestAbility_label", 20 | "exported": true, 21 | "startWindowIcon": "$media:icon", 22 | "startWindowBackground": "$color:start_window_background", 23 | "skills": [ 24 | { 25 | "actions": [ 26 | "action.system.home" 27 | ], 28 | "entities": [ 29 | "entity.system.home" 30 | ] 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/element/color.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": [ 3 | { 4 | "name": "start_window_background", 5 | "value": "#FFFFFF" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "module_test_desc", 5 | "value": "test ability description" 6 | }, 7 | { 8 | "name": "TestAbility_desc", 9 | "value": "the test ability" 10 | }, 11 | { 12 | "name": "TestAbility_label", 13 | "value": "test label" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/751496032/DSBridge-HarmonyOS/7515ee4037c91959c4a7e2244802cf67a3e3a7c3/entry/src/ohosTest/resources/base/media/icon.png -------------------------------------------------------------------------------- /entry/src/ohosTest/resources/base/profile/test_pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": [ 3 | "testability/pages/Index" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /hvigor/hvigor-config.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "dependencies": { 4 | } 5 | } -------------------------------------------------------------------------------- /hvigorfile.ts: -------------------------------------------------------------------------------- 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'; -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /oh_modules 3 | /.preview 4 | /build 5 | /.cxx 6 | /.test -------------------------------------------------------------------------------- /library/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.7.2'; 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 | } -------------------------------------------------------------------------------- /library/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 版本更新记录 3 | 4 | ### 2024//11/15 - 1.7.2 5 | 6 | - 对h5与原生端方法注册不一致的问题进行安全校验。 7 | 8 | ### 2024//10/8 - 1.7.1 9 | 10 | - 原生异步桥接函数支持无参定义;调整代码结构。 11 | 12 | ### 2024/9/27 - 1.7.0 13 | 14 | - 新增异常信息监听; 15 | - 命名空间注册新增校验检测。 16 | 17 | ### 2024/7/8 - 1.6.0 18 | 19 | - 兼容DSBridge2.0 JS脚本 20 | 21 | ### 2024/6/13 - 1.5.2 22 | 23 | - 取消报错toast,由业务自行处理 24 | 25 | ### 2024/5/27 - 1.5.1 26 | 27 | - 修复#12 28 | 29 | ### 2024/5/21 - 1.5.0 30 | 31 | - 修复#8、#11问题; 32 | - 原生同步方法内支持异步串行并发任务,根据鸿蒙特性设计的需求。 33 | 34 | ### 2024/4/19 - 1.3.0 35 | 36 | - 修复#5 、#7问题 37 | 38 | ### 2024/4/6 - 1.2.0 39 | 40 | - 重构适配鸿蒙NEXT版本; 41 | - 支持命名空间API。 42 | 43 | ### 2024/1/14 - 1.1.0 44 | 45 | - 支持检测API是否存在; 46 | - 支持监听JavaScript关闭页面; 47 | - 修复退出页面因异步任务闪退问题。 48 | 49 | ### 2024/1/7 - 1.0.0 50 | 51 | - 鸿蒙原生与JS的桥接交互,兼容现有Android和iOS的dsBridge库的核心功能。 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /library/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 介绍 3 | 4 | HarmonyOS版的DSBridge,通过本库可以在鸿蒙原生与JavaScript完成交互,可以相互调用彼此的功能。 5 | 6 | 目前兼容Android、iOS第三方DSBridge库的核心功能,基本保持原来的使用方式,后续会持续迭代保持与DSBridge库相同的功能,减少前端和客户端的适配工作,另外也会根据鸿蒙的特性做一些定制需求。 7 | 8 | 特性: 9 | 10 | - **已适配鸿蒙NEXT版本;** 11 | - **支持原生同步方法内执行串行异步并发任务,同步等待异步结果,根据鸿蒙特点设计的需求;** 12 | - **兼容DSBridge2.0与3.0 JS脚本;** 13 | - 支持以类的方式集中统一管理API,也支持原生自定义页面组件直接注册使用; 14 | - 支持同步和异步调用; 15 | - 支持进度回调/回传:一次调用,多次返回; 16 | - 支持API是否存在检测; 17 | - 支持Javascript关闭页面的监听与拦截, 18 | - 支持异常信息监听; 19 | - 支持命名空间API。 20 | 21 | 22 | 23 | DSBridge-HarmonyOS已上架录入到[华为鸿蒙生态伙伴组件专区](https://developer.huawei.com/consumer/cn/market/landing/component) 24 | 25 | ![鸿蒙生态市场](https://gitee.com/common-apps/images/raw/master/oh.png) 26 | 27 | 28 | 29 | 源码: 30 | 31 | * github:[DSBridge-HarmonyOS](https://github.com/751496032/DSBridge-HarmonyOS) 32 | * gitee: [DSBridge-HarmonyOS](https://gitee.com/common-apps/dsbrigde-harmony-os) 33 | * [DSBridge-Android](https://github.com/wendux/DSBridge-Android) 34 | * [DSBridge-IOS](https://github.com/wendux/DSBridge-IOS) 35 | 36 | 37 | >由于DSBridge库作者已停止维护,Android端建议使用 https://github.com/751496032/DSBridge-Android ,目前本人在维护。 38 | 39 | 40 | ## 安装 41 | 42 | 安装库: 43 | 44 | ```text 45 | ohpm install @hzw/ohos-dsbridge 46 | ``` 47 | 48 | 49 | ## 基本用法 50 | 51 | ### ArkTS原生侧 52 | 53 | 1、在原生新建一个类`JsBridge`,实现业务API 54 | , 通过类来集中统一管理API,方法用`@JavaScriptInterface()`标注,是不是很眼熟呢,加一个`@JavaScriptInterface()`标注主要为了使用规范,是自定义的装饰器,与Android保持一致性。 55 | ```typescript 56 | export class JsBridge{ 57 | private cHandler: CompleteHandler = null 58 | 59 | /** 60 | * 同步 61 | * @param p 62 | * @returns 63 | */ 64 | @JavaScriptInterface(false) 65 | testSync(p: string): string { 66 | LogUtils.d("testSync: " + JSON.stringify(p)) 67 | return "hello native" 68 | } 69 | 70 | /** 71 | * 异步 72 | * @param p 73 | * @param handler 74 | */ 75 | @JavaScriptInterface() 76 | testAsync(p: string, handler: CompleteHandler) { 77 | LogUtils.d("testAsync: " + JSON.stringify(p)) 78 | this.cHandler = handler 79 | } 80 | } 81 | ``` 82 | 83 | 如果你不希望用一个类来管理API接口,可以在自定义页面组件Component中直接注册使用,然后在组件内定义API接口。 84 | 85 | ```typescript 86 | @Component 87 | @Entry 88 | struct UseInComponentsPage{ 89 | aboutToAppear() 90 | { 91 | // 在一个组件内只能存在一个无命名空间 92 | this.controller.addJavascriptObject(this) 93 | 94 | } 95 | 96 | @JavaScriptInterface(false) 97 | testComponentSync(args: string): string { 98 | return `组件中的同步方法: ${args}` 99 | } 100 | 101 | @JavaScriptInterface() 102 | testComponentAsync(args: string, handler: CompleteHandler) { 103 | handler.complete(`组件中的异步方法: ${args}`) 104 | } 105 | 106 | } 107 | 108 | 109 | ``` 110 | 111 | **API同步方法是不支持用async/await声明,如果需要在同步方法内执行异步任务,可以使用`taskWait()`函数来加持完成,下面会介绍基本用法**;异步方法的形参`CompleteHandler`,可用于结果异步回调。 112 | 113 | 2、在原生Web组件初始化时,通过`WebViewControllerProxy`类来获取`WebviewController`实例来实现JS注入,然后将其关联到Web组件中,接着将API管理类(JsBridge)关联到`WebViewControllerProxy`中。 114 | 115 | ```typescript 116 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 117 | 118 | aboutToAppear() { 119 | this.controller.addJavascriptObject(new JsBridge()) 120 | } 121 | 122 | 123 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 124 | .javaScriptAccess(true) 125 | .javaScriptProxy(this.controller.getJavaScriptProxy()) 126 | .onAlert((event) => { 127 | // AlertDialog.show({ message: event.message }) 128 | return false 129 | }) 130 | 131 | ) 132 | 133 | ``` 134 | 135 | 3、通过`WebViewControllerProxy`调用JavaScript函数。 136 | 137 | ```typescript 138 | Button("调用js函数-同步") 139 | .onClick(() => { 140 | this.controller.callJs("showAlert", [1, 2, '666'], (v) => { 141 | this.msg = v + "" 142 | }) 143 | }) 144 | 145 | Button("调用js函数-异步") 146 | .onClick(() => { 147 | this.controller.callJs("showAlertAsync", [1, 2, '666'], (v) => { 148 | this.msg = v + "" 149 | }) 150 | }) 151 | } 152 | ``` 153 | 154 | `callJs()`方法有三个形参,第一个是Js注册的函数名称,第二个是Js接收函数的参数,是一个数组类型,第三个是监听Js函数返回结果的函数。 155 | 另外也提供了与Android库一样调用函数`callHandler()`。 156 | 157 | 158 | 如果前端使用的是DSBridge2.0的JS脚本,可以通过supportDS2()方法来兼容,如下: 159 | 160 | ```typescript 161 | aboutToAppear() { 162 | // 如果是使用DSBridge2.0 ,调用supportDS2方法 163 | this.controller.supportDS2(true) 164 | // 以下两种注册方式都可以,任选其一 165 | this.controller.addJavascriptObject(this) 166 | // this.controller.addJavascriptObject(new JsBridge2()) 167 | // DS2.0脚本不支持API命令空间 168 | // this.controller.addJavascriptObject(new JsBridge2(),'js2') 169 | // 开启调试模式 170 | webview.WebviewController.setWebDebuggingAccess(true); 171 | } 172 | ``` 173 | 174 | 175 | 176 | ### JavaScript侧 177 | 178 | 1、在JavaScript中初始化dsBridge,通过cdn或者npm安装都可以。 179 | 180 | 如果项目没有历史包袱,建议直接用`m-dsbridge`包。 181 | 182 | ``` 183 | npm i m-dsbridge 184 | // 或者cdn引入 185 | 186 | ``` 187 | 188 | 也支持直接用原Android或iOS的[DSBridge库](https://github.com/wendux/DSBridge-Android)的JS脚本。 189 | 190 | ```typescript 191 | https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js 192 | ``` 193 | 194 | > 如果m-dsbridge 发生异常,建议切换到 https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js 195 | 196 | 2、通过`dsBridge`对象注册Js函数,供原生调用。 197 | 198 | ```typescript 199 | // 注册同步函数 200 | dsBridge.register('showAlert', function (a, b, c) { 201 | // return "原生调用JS showAlert函数" 202 | alert("原生调用JS showAlert函数" + a + " " + b + " " + c) 203 | return true 204 | }) 205 | 206 | // 注册异步函数 207 | dsBridge.registerAsyn('showAlertAsync', function (a, b, c, callback) { 208 | let counter = 0 209 | let id = setInterval(() => { 210 | if (counter < 5) { 211 | callback(counter, false) 212 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 213 | counter++ 214 | } else { 215 | callback(counter, true) 216 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 217 | clearInterval(id) 218 | } 219 | }, 1000) 220 | 221 | }) 222 | ``` 223 | 224 | 其中异步的`callback`函数,如果最后一个参数返回`true`则完成整个链接的调用,`false`则可以一直回调给原生,这个就是JavaScript端的一次调用,多次返回。比如需要将JavaScript端进度数据不间断同步到原生,这时就可以派上用场了。 225 | 226 | 227 | 3、通过`dsBridge`对象调用原生API,第一个参数是原生方法名称,第二参数是原生方法接收的参数,异步方法有第三个参数是回调函数,会接收`CompleteHandler`异步回调结果。 228 | 229 | ```typescript 230 | // 同步 231 | let msg = dsBridge.call('testSync', JSON.stringify({data: 100})) 232 | 233 | // 异步, 参数可以是对象或者基本类型 234 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 235 | updateMsg(msg) 236 | }) 237 | 238 | // 如果你使用的是DSBridge 2.0脚本,异步调用的参数必须是一个对象 239 | dsBridge.call('testAsync', {data: 200}, (msg) => { 240 | updateMsg(msg) 241 | }) 242 | ``` 243 | 244 | 245 | 246 | 247 | ## 进度回调(一次调用,多次返回) 248 | 249 | 前面提到了JavaScript端的一次调用,多次回调的情况,在原生端也是支持的,还是有应用场景的,比如将原生的下载进度实时同步到js中,可以通过`CompleteHandler#setProgressData()`方法来实现。 250 | 251 | ```typescript 252 | @JavaScriptInterface() 253 | testAsync(p: string, handler: CompleteHandler) { 254 | LogUtils.d("testAsync: " + JSON.stringify(p)) 255 | this.cHandler = handler 256 | let counter = 0 257 | setInterval(() => { 258 | if (counter < 5) { 259 | counter++ 260 | handler.setProgressData("异步返回的数据--" + counter) 261 | } else { 262 | this.cHandler.complete("异步返回的数据--结束") 263 | this.cHandler.complete("异步返回的数据--结束2") 264 | } 265 | }, 1000) 266 | ``` 267 | JavaScript: 268 | 269 | ```typescript 270 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 271 | updateMsg(msg) 272 | }) 273 | ``` 274 | 275 | ## 监听或拦截Javascript关闭页面 276 | 277 | Js调用`close()`函数可以关闭当前页面,原生可以设置监听观察是否拦截。 278 | 279 | ```typescript 280 | aboutToAppear() { 281 | this.controller.setClosePageListener(() => { 282 | return true; // false 会拦截关闭页面 283 | }) 284 | } 285 | ``` 286 | 287 | 在回调函数中如果返回`false`,会拦截掉关闭页面的事件。 288 | 289 | ## 销毁结束任务 290 | 291 | 如果异步任务还在执行中,比如`setProgressData`,此时关闭页面返回就会闪退,为了避免这种情形,建议在组件的生命周期函数`aboutToDisappear()`中结束任务。 292 | 293 | ```typescript 294 | aboutToDisappear(){ 295 | this.jsBridge.destroy() 296 | } 297 | 298 | ``` 299 | 300 | ## 命名空间 301 | 302 | 命名空间可以帮助你更好的管理API,这在API数量多的时候非常实用,支持你通过命名空间将API分类管理,不同级之间只需用'.' 分隔即可。支持同步与异步方式使用。 303 | 304 | 305 | ### ArkTS API命令空间 306 | 307 | 原生用`WebViewControllerProxy#addJavascriptObject` 指定一个命名空间名称: 308 | 309 | ```typescript 310 | this.controller.addJavascriptObject(new JsBridgeNamespace(), "namespace") 311 | ``` 312 | 313 | 314 | 在JavaScript中,用命名空间名称`.`对应的原生函数。 315 | 316 | ```javascript 317 | const callNative6 = () => { 318 | let msg = dsBridge.call('namespace.testSync',{msg:'来自js命名空间的数据'}) 319 | updateMsg(msg) 320 | } 321 | 322 | const callNative7 = () => { 323 | dsBridge.call('namespace.testAsync', 'test', (msg) => { 324 | updateMsg(msg) 325 | }) 326 | } 327 | 328 | ``` 329 | 330 | ### JavaScript API命令空间 331 | 332 | 用dsBridge对象注册js函数的命名空间。 333 | 334 | ```javascript 335 | // namespace 336 | dsBridge.register('sync', { 337 | test: function (a, b) { 338 | return "namespace: " + (a + b) 339 | } 340 | }) 341 | 342 | dsBridge.registerAsyn("asny",{ 343 | test: function (a,b ,callback) { 344 | callback("namespace: " + (a + b)) 345 | } 346 | }) 347 | ``` 348 | 349 | 第一个参数命名空间的名称,比如`sync`,第二个参数是API业务对象实例,支持字面量对象和Class类实例。 350 | 351 | 在原生调用方式: 352 | 353 | ```typescript 354 | 355 | this.controller.callJs("sync.test", [1, 2], (value: string) => { 356 | this.msg = value 357 | }) 358 | 359 | this.controller.callJs("asny.test", [3, 2], (value: string) => { 360 | this.msg = value 361 | }) 362 | ``` 363 | 364 | ## 原生同步方法内执行串行异步并发任务 365 | 366 | 原生同步方法: 367 | 368 | ```typescript 369 | /** 370 | * 同步模版 371 | * @param p 372 | * @returns 373 | */ 374 | @JavaScriptInterface(false) 375 | testSync(p: string): string { 376 | LogUtils.d("testSync: " + JSON.stringify(p)) 377 | return "原生同步testSync方法返回的数据" 378 | } 379 | ``` 380 | 381 | 如果要在同步方法内执行异步任务,并将异步结果立即返回给h5,上面的设计显然是无法满足需求的;在鸿蒙中异步任务基本与Promise和async/await有关联,然而桥接函数是不支持使用async/await声明(主要是受鸿蒙Web脚本注入机制的限制,也是为了考虑兼容Android/iOS项目而因此这样设计的)。 382 | 383 | 对此,设计了一个`taskWait()`函数来满足上述的需求,可以通过`taskWait()`函数在主线程的同步方法内执行串行并发异步任务,主线程会同步等待异步结果。 384 | 385 | ```typescript 386 | /** 387 | * 同步方法中执行异步并发任务 388 | * @param args 389 | * @returns 390 | */ 391 | @JavaScriptInterface(false) 392 | testTaskWait(args: string): number { 393 | let p = new Param(100, 200) 394 | let p1 = new Param(100, 300) 395 | taskWait(p) 396 | p1.tag = "Param" 397 | taskWait(p1) 398 | LogUtils.d(`testTaskWait sum: ${p.sum} ${p1.sum}`) 399 | return p.sum + p1.sum 400 | } 401 | 402 | ``` 403 | 404 | 其中`Param`类需要继承`BaseSendable`,同时用`@Sendable`装饰器声明,任务放在`run()`方法中执行。 405 | 406 | ```typescript 407 | @Sendable 408 | export class Param extends BaseSendable{ 409 | private a : number = 0 410 | private b : number = 0 411 | public sum: number = 0 412 | public enableLog: boolean = true 413 | 414 | constructor(a: number, b: number) { 415 | super(); 416 | this.a = a; 417 | this.b = b; 418 | } 419 | // 异步任务执行 420 | async run(): Promise { 421 | this.sum = await this.add() 422 | } 423 | 424 | async add(){ 425 | return this.a + this.b 426 | } 427 | 428 | 429 | } 430 | ``` 431 | 432 | `taskWait()`函数是一个轻量级的同步等待函数,不建议执行耗时过长的任务,如果在3s内没有完成任务,会自动结束等待将结果返回,可能会存在数据丢失的情况;对于特别耗时的任务建议使用异步桥接函数。 433 | 434 | ## 监听异常 435 | 436 | 可以通过setGlobalErrorMessageListener()方法来监听调用异常,如下: 437 | 438 | 439 | ```typescript 440 | this.controller.setGlobalErrorMessageListener((err: string) => { 441 | promptAction.showDialog({title:`${err}`}) 442 | }) 443 | 444 | ``` 445 | 446 | ## 交流 447 | 448 | 如有疑问,请提issues, 或加v进群交流:751496032,备注鸿蒙 449 | 450 | 451 | ## 最后 452 | 453 | 感谢[@name718](https://github.com/name718)、[@fjc0k](https://github.com/fjc0k)等各位大佬的支持与反馈。欢迎大家多多反馈,一起支持完善鸿蒙生态。 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | -------------------------------------------------------------------------------- /library/build-profile.json5: -------------------------------------------------------------------------------- 1 | { 2 | "apiType": "stageMode", 3 | "buildOption": { 4 | // "artifactType": "original" 5 | }, 6 | "targets": [ 7 | { 8 | "name": "default", 9 | "output": { 10 | "artifactName": "dsbridge" 11 | } 12 | } 13 | ], 14 | } -------------------------------------------------------------------------------- /library/example/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 介绍 3 | 4 | HarmonyOS版的DSBridge,通过本库可以在鸿蒙原生与JavaScript完成交互,可以相互调用彼此的功能。 5 | 6 | 目前兼容Android、iOS第三方DSBridge库的核心功能,基本保持原来的使用方式,后续会持续迭代保持与DSBridge库相同的功能,减少前端和客户端的适配工作,另外也会根据鸿蒙的特性做一些定制需求。 7 | 8 | 特性: 9 | 10 | - **已适配鸿蒙NEXT版本;** 11 | - **支持原生同步方法内执行串行异步并发任务,同步等待异步结果,根据鸿蒙特点设计的需求;** 12 | - **兼容DSBridge2.0与3.0 JS脚本;** 13 | - 支持以类的方式集中统一管理API,也支持原生自定义页面组件直接注册使用; 14 | - 支持同步和异步调用; 15 | - 支持进度回调/回传:一次调用,多次返回; 16 | - 支持API是否存在检测; 17 | - 支持Javascript关闭页面的监听与拦截, 18 | - 支持命名空间API。 19 | 20 | 21 | 源码: 22 | 23 | * [DSBridge-HarmonyOS](https://github.com/751496032/DSBridge-HarmonyOS) 24 | * [DSBridge-Android](https://github.com/wendux/DSBridge-Android) 25 | * [DSBridge-IOS](https://github.com/wendux/DSBridge-IOS) 26 | 27 | 28 | >由于DSBridge库作者已停止维护,Android端建议使用 https://github.com/751496032/DSBridge-Android ,目前本人在维护。 29 | 30 | 31 | ## 安装 32 | 33 | 安装库: 34 | 35 | ```text 36 | ohpm install @hzw/ohos-dsbridge 37 | ``` 38 | 39 | 或者安装本地har包 40 | 41 | ```text 42 | ohpm install ../libs/library.har 43 | ``` 44 | 45 | ## 基本用法 46 | 47 | ### ArkTS原生侧 48 | 49 | 1、在原生新建一个类`JsBridge`,实现业务API 50 | , 通过类来集中统一管理API,方法用`@JavaScriptInterface()`标注,是不是很眼熟呢,加一个`@JavaScriptInterface()`标注主要为了使用规范,是自定义的装饰器,与Android保持一致性。 51 | ```typescript 52 | export class JsBridge{ 53 | private cHandler: CompleteHandler = null 54 | 55 | /** 56 | * 同步 57 | * @param p 58 | * @returns 59 | */ 60 | @JavaScriptInterface(false) 61 | testSync(p: string): string { 62 | LogUtils.d("testSync: " + JSON.stringify(p)) 63 | return "hello native" 64 | } 65 | 66 | /** 67 | * 异步 68 | * @param p 69 | * @param handler 70 | */ 71 | @JavaScriptInterface() 72 | testAsync(p: string, handler: CompleteHandler) { 73 | LogUtils.d("testAsync: " + JSON.stringify(p)) 74 | this.cHandler = handler 75 | } 76 | } 77 | ``` 78 | 79 | 如果你不希望用一个类来管理API接口,可以在自定义页面组件Component中直接注册使用,然后在组件内定义API接口。 80 | 81 | ```typescript 82 | @Component 83 | @Entry 84 | struct UseInComponentsPage{ 85 | aboutToAppear() 86 | { 87 | // 在一个组件内只能存在一个无命名空间 88 | this.controller.addJavascriptObject(this) 89 | 90 | } 91 | 92 | @JavaScriptInterface(false) 93 | testComponentSync(args: string): string { 94 | return `组件中的同步方法: ${args}` 95 | } 96 | 97 | @JavaScriptInterface() 98 | testComponentAsync(args: string, handler: CompleteHandler) { 99 | handler.complete(`组件中的异步方法: ${args}`) 100 | } 101 | 102 | } 103 | 104 | 105 | ``` 106 | 107 | **API同步方法是不支持用async/await声明,如果需要在同步方法内执行异步任务,可以使用`taskWait()`函数来加持完成,下面会介绍基本用法**;异步方法的形参`CompleteHandler`,可用于结果异步回调。 108 | 109 | 2、在原生Web组件初始化时,通过`WebViewControllerProxy`类来获取`WebviewController`实例来实现JS注入,然后将其关联到Web组件中,接着将API管理类(JsBridge)关联到`WebViewControllerProxy`中。 110 | 111 | ```typescript 112 | private controller: WebViewControllerProxy = WebViewControllerProxy.createController() 113 | 114 | aboutToAppear() { 115 | this.controller.addJavascriptObject(new JsBridge()) 116 | } 117 | 118 | 119 | Web({ src: this.localPath, controller: this.controller.getWebViewController() }) 120 | .javaScriptAccess(true) 121 | .javaScriptProxy(this.controller.getJavaScriptProxy()) 122 | .onAlert((event) => { 123 | // AlertDialog.show({ message: event.message }) 124 | return false 125 | }) 126 | 127 | ) 128 | 129 | ``` 130 | 131 | 3、通过`WebViewControllerProxy`调用JavaScript函数。 132 | 133 | ```typescript 134 | Button("调用js函数-同步") 135 | .onClick(() => { 136 | this.controller.callJs("showAlert", [1, 2, '666'], (v) => { 137 | this.msg = v + "" 138 | }) 139 | }) 140 | 141 | Button("调用js函数-异步") 142 | .onClick(() => { 143 | this.controller.callJs("showAlertAsync", [1, 2, '666'], (v) => { 144 | this.msg = v + "" 145 | }) 146 | }) 147 | } 148 | ``` 149 | 150 | `callJs()`方法有三个形参,第一个是Js注册的函数名称,第二个是Js接收函数的参数,是一个数组类型,第三个是监听Js函数返回结果的函数。 151 | 另外也提供了与Android库一样调用函数`callHandler()`。 152 | 153 | 154 | 如果前端使用的是DSBridge2.0的JS脚本,可以通过supportDS2()方法来兼容,如下: 155 | 156 | ```typescript 157 | aboutToAppear() { 158 | // 如果是使用DSBridge2.0 ,调用supportDS2方法 159 | this.controller.supportDS2(true) 160 | // 以下两种注册方式都可以,任选其一 161 | this.controller.addJavascriptObject(this) 162 | // this.controller.addJavascriptObject(new JsBridge2()) 163 | // DS2.0脚本不支持API命令空间 164 | // this.controller.addJavascriptObject(new JsBridge2(),'js2') 165 | // 开启调试模式 166 | webview.WebviewController.setWebDebuggingAccess(true); 167 | } 168 | ``` 169 | 170 | 171 | 172 | ### JavaScript侧 173 | 174 | 1、在JavaScript中初始化dsBridge,通过cdn或者npm安装都可以。 175 | 176 | 如果项目没有历史包袱,建议直接用`m-dsbridge`包。 177 | 178 | ``` 179 | npm i m-dsbridge 180 | // 或者cdn引入 181 | 182 | ``` 183 | 184 | 也支持直接用原Android或iOS的[DSBridge库](https://github.com/wendux/DSBridge-Android)的JS脚本。 185 | 186 | ```typescript 187 | https://cdn.jsdelivr.net/npm/dsbridge/dist/dsbridge.js 188 | ``` 189 | 190 | 2、通过`dsBridge`对象注册Js函数,供原生调用。 191 | 192 | ```typescript 193 | // 注册同步函数 194 | dsBridge.register('showAlert', function (a, b, c) { 195 | // return "原生调用JS showAlert函数" 196 | alert("原生调用JS showAlert函数" + a + " " + b + " " + c) 197 | return true 198 | }) 199 | 200 | // 注册异步函数 201 | dsBridge.registerAsyn('showAlertAsync', function (a, b, c, callback) { 202 | let counter = 0 203 | let id = setInterval(() => { 204 | if (counter < 5) { 205 | callback(counter, false) 206 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 207 | counter++ 208 | } else { 209 | callback(counter, true) 210 | alert("原生调用JS showAlertAsync函数" + a + " " + b + " " + c + " " + counter) 211 | clearInterval(id) 212 | } 213 | }, 1000) 214 | 215 | }) 216 | ``` 217 | 218 | 其中异步的`callback`函数,如果最后一个参数返回`true`则完成整个链接的调用,`false`则可以一直回调给原生,这个就是JavaScript端的一次调用,多次返回。比如需要将JavaScript端进度数据不间断同步到原生,这时就可以派上用场了。 219 | 220 | 221 | 3、通过`dsBridge`对象调用原生API,第一个参数是原生方法名称,第二参数是原生方法接收的参数,异步方法有第三个参数是回调函数,会接收`CompleteHandler`异步回调结果。 222 | 223 | ```typescript 224 | // 同步 225 | let msg = dsBridge.call('testSync', JSON.stringify({data: 100})) 226 | 227 | // 异步 228 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 229 | updateMsg(msg) 230 | }) 231 | ``` 232 | 233 | 234 | 235 | 236 | ## 进度回调(一次调用,多次返回) 237 | 238 | 前面提到了JavaScript端的一次调用,多次回调的情况,在原生端也是支持的,还是有应用场景的,比如将原生的下载进度实时同步到js中,可以通过`CompleteHandler#setProgressData()`方法来实现。 239 | 240 | ```typescript 241 | @JavaScriptInterface() 242 | testAsync(p: string, handler: CompleteHandler) { 243 | LogUtils.d("testAsync: " + JSON.stringify(p)) 244 | this.cHandler = handler 245 | let counter = 0 246 | setInterval(() => { 247 | if (counter < 5) { 248 | counter++ 249 | handler.setProgressData("异步返回的数据--" + counter) 250 | } else { 251 | this.cHandler.complete("异步返回的数据--结束") 252 | this.cHandler.complete("异步返回的数据--结束2") 253 | } 254 | }, 1000) 255 | ``` 256 | JavaScript: 257 | 258 | ```typescript 259 | dsBridge.call('testAsync', JSON.stringify({data: 200}), (msg) => { 260 | updateMsg(msg) 261 | }) 262 | ``` 263 | 264 | ## 监听或拦截Javascript关闭页面 265 | 266 | Js调用`close()`函数可以关闭当前页面,原生可以设置监听观察是否拦截。 267 | 268 | ```typescript 269 | aboutToAppear() { 270 | this.controller.setClosePageListener(() => { 271 | return true; // false 会拦截关闭页面 272 | }) 273 | } 274 | ``` 275 | 276 | 在回调函数中如果返回`false`,会拦截掉关闭页面的事件。 277 | 278 | ## 销毁结束任务 279 | 280 | 如果异步任务还在执行中,比如`setProgressData`,此时关闭页面返回就会闪退,为了避免这种情形,建议在组件的生命周期函数`aboutToDisappear()`中结束任务。 281 | 282 | ```typescript 283 | aboutToDisappear(){ 284 | this.jsBridge.destroy() 285 | } 286 | 287 | ``` 288 | 289 | ## 命名空间 290 | 291 | 命名空间可以帮助你更好的管理API,这在API数量多的时候非常实用,支持你通过命名空间将API分类管理,不同级之间只需用'.' 分隔即可。支持同步与异步方式使用。 292 | 293 | 294 | ### ArkTS API命令空间 295 | 296 | 原生用`WebViewControllerProxy#addJavascriptObject` 指定一个命名空间名称: 297 | 298 | ```typescript 299 | this.controller.addJavascriptObject(new JsBridgeNamespace(), "namespace") 300 | ``` 301 | 302 | 303 | 在JavaScript中,用命名空间名称`.`对应的原生函数。 304 | 305 | ```javascript 306 | const callNative6 = () => { 307 | let msg = dsBridge.call('namespace.testSync',{msg:'来自js命名空间的数据'}) 308 | updateMsg(msg) 309 | } 310 | 311 | const callNative7 = () => { 312 | dsBridge.call('namespace.testAsync', 'test', (msg) => { 313 | updateMsg(msg) 314 | }) 315 | } 316 | 317 | ``` 318 | 319 | ### JavaScript API命令空间 320 | 321 | 用dsBridge对象注册js函数的命名空间。 322 | 323 | ```javascript 324 | // namespace 325 | dsBridge.register('sync', { 326 | test: function (a, b) { 327 | return "namespace: " + (a + b) 328 | } 329 | }) 330 | 331 | dsBridge.registerAsyn("asny",{ 332 | test: function (a,b ,callback) { 333 | callback("namespace: " + (a + b)) 334 | } 335 | }) 336 | ``` 337 | 338 | 第一个参数命名空间的名称,比如`sync`,第二个参数是API业务对象实例,支持字面量对象和Class类实例。 339 | 340 | 在原生调用方式: 341 | 342 | ```typescript 343 | 344 | this.controller.callJs("sync.test", [1, 2], (value: string) => { 345 | this.msg = value 346 | }) 347 | 348 | this.controller.callJs("asny.test", [3, 2], (value: string) => { 349 | this.msg = value 350 | }) 351 | ``` 352 | 353 | ## 原生同步方法内执行串行异步并发任务 354 | 355 | 原生同步方法: 356 | 357 | ```typescript 358 | /** 359 | * 同步模版 360 | * @param p 361 | * @returns 362 | */ 363 | @JavaScriptInterface(false) 364 | testSync(p: string): string { 365 | LogUtils.d("testSync: " + JSON.stringify(p)) 366 | return "原生同步testSync方法返回的数据" 367 | } 368 | ``` 369 | 370 | 如果要在同步方法内执行异步任务,并将异步结果立即返回给h5,上面的设计显然是无法满足需求的;在鸿蒙中异步任务基本与Promise和async/await有关联,然而桥接函数是不支持使用async/await声明(主要是受鸿蒙Web脚本注入机制的限制,也是为了考虑兼容Android/iOS项目而因此这样设计的)。 371 | 372 | 对此,设计了一个`taskWait()`函数来满足上述的需求,可以通过`taskWait()`函数在主线程的同步方法内执行串行并发异步任务,主线程会同步等待异步结果。 373 | 374 | ```typescript 375 | /** 376 | * 同步方法中执行异步并发任务 377 | * @param args 378 | * @returns 379 | */ 380 | @JavaScriptInterface(false) 381 | testTaskWait(args: string): number { 382 | let p = new Param(100, 200) 383 | let p1 = new Param(100, 300) 384 | taskWait(p) 385 | p1.tag = "Param" 386 | taskWait(p1) 387 | LogUtils.d(`testTaskWait sum: ${p.sum} ${p1.sum}`) 388 | return p.sum + p1.sum 389 | } 390 | 391 | ``` 392 | 393 | 其中`Param`类需要继承`BaseSendable`,同时用`@Sendable`装饰器声明,任务放在`run()`方法中执行。 394 | 395 | ```typescript 396 | @Sendable 397 | export class Param extends BaseSendable{ 398 | private a : number = 0 399 | private b : number = 0 400 | public sum: number = 0 401 | public enableLog: boolean = true 402 | 403 | constructor(a: number, b: number) { 404 | super(); 405 | this.a = a; 406 | this.b = b; 407 | } 408 | // 异步任务执行 409 | async run(): Promise { 410 | this.sum = await this.add() 411 | } 412 | 413 | async add(){ 414 | return this.a + this.b 415 | } 416 | 417 | 418 | } 419 | ``` 420 | 421 | `taskWait()`函数是一个轻量级的同步等待函数,不建议执行耗时过长的任务,如果在3s内没有完成任务,会自动结束等待将结果返回,可能会存在数据丢失的情况;对于特别耗时的任务建议使用异步桥接函数。 422 | 423 | 424 | 425 | 426 | ## 最后 427 | 428 | 感谢[@name718](https://github.com/name718)、[@fjc0k](https://github.com/fjc0k)等各位大佬的支持与反馈。欢迎大家多多反馈,一起支持完善鸿蒙生态。 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | -------------------------------------------------------------------------------- /library/hvigorfile.ts: -------------------------------------------------------------------------------- 1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. 2 | export { harTasks } from '@ohos/hvigor-ohos-plugin'; -------------------------------------------------------------------------------- /library/index.ets: -------------------------------------------------------------------------------- 1 | export { MainPage } from './src/main/ets/components/mainpage/MainPage' 2 | 3 | export { BaseBridge } from './src/main/ets/core/BaseBridge' 4 | 5 | export { IWebViewControllerProxy as WebViewInterface } from './src/main/ets/core/WebViewInterface' 6 | 7 | export { WebViewControllerProxy } from './src/main/ets/core/WebViewControllerProxy' 8 | 9 | export * from "./src/main/ets/core/Entity" 10 | 11 | export { BaseSendable } from './src/main/ets/wait/BaseSendable' 12 | 13 | export { taskWait } from './src/main/ets/wait/TaskWait' 14 | 15 | export { LogUtils } from './src/main/ets/utils/LogUtils' 16 | -------------------------------------------------------------------------------- /library/oh-package-lock.json5: -------------------------------------------------------------------------------- 1 | { 2 | "meta": { 3 | "stableOrder": false 4 | }, 5 | "lockfileVersion": 3, 6 | "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", 7 | "specifiers": { 8 | "reflect-metadata@^0.1.13": "reflect-metadata@0.1.13" 9 | }, 10 | "packages": { 11 | "reflect-metadata@0.1.13": { 12 | "name": "reflect-metadata", 13 | "version": "0.1.13", 14 | "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", 15 | "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.1.13.tgz", 16 | "shasum": "67ae3ca57c972a2aa1642b10fe363fe32d49dc08", 17 | "registryType": "ohpm" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /library/oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "license": "Apache-2.0", 3 | "devDependencies": {}, 4 | "keywords": [ 5 | "JavaScript", 6 | "鸿蒙", 7 | "harmonyOS", 8 | "dsBridge", 9 | "android", 10 | "JsBridge" 11 | ], 12 | "author": "HZWei", 13 | "name": "@hzw/ohos-dsbridge", 14 | "description": "HarmonyOS native and JS interaction, calling each other\u0027s functions", 15 | "main": "index.ets", 16 | "repository": "https://github.com/751496032/DSBridge-HarmonyOS.git", 17 | "homepage": "https://gitee.com/common-apps/dsbrigde-harmony-os.git", 18 | "version": "1.7.2", 19 | "dependencies": { 20 | "reflect-metadata": "^0.1.13" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /library/src/main/ets/components/mainpage/MainPage.ets: -------------------------------------------------------------------------------- 1 | @Component 2 | export struct MainPage { 3 | @State message: string = 'Hello World' 4 | 5 | build() { 6 | Row() { 7 | Column() { 8 | Text(this.message) 9 | .fontSize(50) 10 | .fontWeight(FontWeight.Bold) 11 | } 12 | .width('100%') 13 | } 14 | .height('100%') 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /library/src/main/ets/core/BaseBridge.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallResult, 3 | MetaData, 4 | JsInterface, 5 | JavaScriptProxy, 6 | Parameter, 7 | NativeCallInfo, 8 | OnReturnValue, 9 | CompleteHandler, 10 | JavaScriptInterface, 11 | OnCloseWindowListener, 12 | Args, 13 | OnErrorMessageListener, 14 | NativeMethodParam 15 | } from './Entity' 16 | import "reflect-metadata" 17 | import { LogUtils } from '../utils/LogUtils' 18 | import router from '@ohos.router' 19 | import { IBaseBridge, IWebViewControllerProxy } from './WebViewInterface' 20 | import { ToastUtils } from '../utils/ToastUtils' 21 | import { JSON } from '@kit.ArkTS' 22 | 23 | export class BaseBridge implements JsInterface, IBaseBridge { 24 | 25 | private controller: IWebViewControllerProxy 26 | private name: string = "_dsbridge" 27 | private isInject: boolean = true 28 | private callID: number = 0 29 | private handlerMap = new Map() 30 | private jsClosePageListener?: OnCloseWindowListener 31 | private interrupt = false 32 | private _isSupportDS2: boolean = false 33 | 34 | 35 | private onErrorListener?: OnErrorMessageListener 36 | 37 | supportDS2(enable: boolean): void { 38 | this._isSupportDS2 = enable 39 | } 40 | 41 | public get isSupportDS2(): boolean { 42 | return this._isSupportDS2 43 | } 44 | 45 | setWebViewControllerProxy(controller: IWebViewControllerProxy){ 46 | this.controller = controller 47 | } 48 | 49 | setGlobalErrorMessageListener(listener: OnErrorMessageListener): void { 50 | this.onErrorListener = listener 51 | } 52 | 53 | 54 | javaScriptProxy(): JavaScriptProxy { 55 | let list = this._isSupportDS2 ? ['call', 'returnValue'] : ['call'] 56 | return { 57 | object: this, 58 | name: this.name, 59 | methodList: list, 60 | controller: this.controller 61 | } 62 | } 63 | 64 | /** 65 | * @deprecated 66 | */ 67 | injectJavaScript = (name: string) => { 68 | if (this.isInject) { 69 | this.controller.registerJavaScriptProxy(this, name, 70 | ['call']) 71 | this.controller.refresh() 72 | this.isInject = false 73 | } 74 | 75 | } 76 | 77 | private isObject(val: Object) { 78 | return val !== null && typeof val === 'object'; 79 | } 80 | 81 | private isNotEmpty(val: Object): boolean { 82 | return !this.isEmpty(val) 83 | } 84 | 85 | private isEmpty(val: Object): boolean { 86 | let isEmpty = val === undefined || val === null 87 | if (typeof val === 'string') { 88 | return isEmpty || val.trim().length <= 0 89 | } 90 | return isEmpty 91 | } 92 | 93 | 94 | /** 95 | * 96 | * @param methodName 原生方法名 97 | * @param params js携带的参数 对应实体类Parameter 98 | * @returns result#code == 0, 则整个流程正常调用 99 | */ 100 | call = (methodName: string, params: string): string => { 101 | const error = "Js bridge called, but can't find a corresponded " + 102 | "JavascriptInterface object , please check your code!" 103 | let result: CallResult = { code: -1 } 104 | if (this.isEmpty(methodName)) { 105 | return this.handlerError(result, error) 106 | } 107 | const m = this.parseNamespace(methodName) 108 | const obj = this.controller.javaScriptNamespaceInterfaces.get(m[0]) 109 | methodName = m[1] 110 | LogUtils.d("call methodName: " + methodName + " params: " + params + ' ' + (obj === this)) 111 | if (this.isEmpty(obj)) { 112 | return this.handlerError(result, error) 113 | } 114 | const method = Reflect.get(this.isNotEmpty(obj) ? obj : this, methodName); 115 | 116 | if (typeof method !== 'function') { 117 | const err = `call failed: ${methodName} is not a method or is not defined` 118 | return this.handlerError(result, err) 119 | } 120 | let async: boolean = false 121 | if (this.isNotEmpty(method)) { 122 | const decorator = Reflect.getMetadata(MetaData.METHOD_DECORATE, method) 123 | if (!decorator) { 124 | const err = `call failed: please add @JavaScriptInterface decorator in the ${methodName} method` 125 | return this.handlerError(result, err) 126 | } 127 | async = Reflect.getMetadata(MetaData.ASYNC, method) ?? false 128 | } else { 129 | const err = `call failed: 【${methodName}】 method is undefined ` 130 | return this.handlerError(result, err) 131 | } 132 | 133 | let jsParam: Parameter = this.safeParse(params) 134 | // async = (this.isNotEmpty(jsParam._dscbstub)) || async 135 | LogUtils.d(`call async: ${async}`) 136 | if (!async && this.isNotEmpty(jsParam._dscbstub)) { 137 | const err = 'call failed: h5 async differs from native registration.' 138 | return this.handlerError(result, err) 139 | } 140 | let data: string = (this.isObject(jsParam.data) ? JSON.stringify(jsParam.data) : jsParam.data) as string 141 | if (this._isSupportDS2) { 142 | if (async) { 143 | let newParam = this.safeParse(JSON.stringify(jsParam)) 144 | delete newParam._dscbstub 145 | data = JSON.stringify(newParam) 146 | } else { 147 | data = params 148 | } 149 | 150 | } 151 | if (async) { 152 | const handler = { 153 | complete: (value: Args) => { 154 | result.code = 0 155 | result.data = value 156 | this.callbackToJs(jsParam, result) 157 | }, 158 | setProgressData: (value: Args) => { 159 | result.code = 0 160 | result.data = value 161 | this.callbackToJs(jsParam, result, false) 162 | }, 163 | 164 | } 165 | try { 166 | let len = method.length 167 | 168 | if (len === 0) { 169 | result.code = 0 170 | method.call(obj) 171 | } else if (len === 1) { 172 | result.code = 0 173 | method.call(obj, handler) 174 | } else if (len === 2) { 175 | result.code = 0 176 | method.call(obj, data, handler) 177 | } else { 178 | const err = `call failed: (${methodName}) method parameter number error` 179 | return this.handlerError(result, err) 180 | } 181 | } catch (e) { 182 | return this.handlerError(result, JSON.stringify(e)) 183 | } 184 | 185 | } else { 186 | const r = method.call(obj, data); 187 | result.code = 0 188 | result.data = r 189 | } 190 | 191 | return this._isSupportDS2 ? result.data?.toString() : JSON.stringify(result) 192 | 193 | } 194 | 195 | 196 | private handlerError(result: CallResult, err: string) { 197 | result.errMsg = err 198 | LogUtils.e(err) 199 | // 不再弹出toast,由业务自行处理 200 | // ToastUtils.show(err) 201 | this.onErrorListener?.(err) 202 | return JSON.stringify(result) 203 | } 204 | 205 | private parseNamespace(method: string): string[] { 206 | let pos = method.lastIndexOf('.') 207 | let namespace = '' 208 | if (pos != -1) { 209 | namespace = method.substring(0, pos) 210 | method = method.substring(pos + 1) 211 | } 212 | return [namespace, method] 213 | } 214 | 215 | private safeParse(json: string): Parameter { 216 | let data: Parameter = {} 217 | if (this.isEmpty(json)) { 218 | return data 219 | } 220 | 221 | try { 222 | data = JSON.parse(json) 223 | } catch (e) { 224 | LogUtils.e(e) 225 | const msg = JSON.stringify(e) 226 | this.handlerError({ errMsg: msg } as CallResult, msg) 227 | } 228 | return data 229 | 230 | } 231 | 232 | private callbackToJs = (jsParam: Parameter, result: CallResult, complete: boolean = true) => { 233 | if (this.interrupt) return; 234 | let args = JSON.stringify(result) 235 | let callbackName = jsParam._dscbstub 236 | LogUtils.d(`callbackToJs : ${callbackName}(${args})`) 237 | let script = `${callbackName}(${args}.data);` 238 | if (complete) { 239 | script += "delete window." + callbackName 240 | } 241 | this.controller.runJavaScript(script) 242 | } 243 | 244 | callJs(method: string, args?: any[], jsReturnValueHandler?: OnReturnValue) { 245 | if (this.interrupt) return; 246 | const callInfo = { 247 | method, 248 | callbackId: ++this.callID, 249 | data: args ? JSON.stringify(args) : '' 250 | } 251 | let script = `(window._dsf.${method}||window.${method}).apply(window._dsf||window,${JSON.stringify(args)})` 252 | if (jsReturnValueHandler != null) { 253 | if (this._isSupportDS2) { 254 | script = `${this.name}.returnValue(${callInfo.callbackId},${script})` 255 | } 256 | this.handlerMap.set(callInfo.callbackId, jsReturnValueHandler) 257 | } 258 | if (this._isSupportDS2) { 259 | this.controller.runJavaScript(script) 260 | }else { 261 | const arg = JSON.stringify(callInfo) 262 | this.controller.runJavaScript(`window._handleMessageFromNative(${arg})`) 263 | } 264 | 265 | } 266 | 267 | callJsNoParam(method: string, jsReturnValueHandler?: OnReturnValue) { 268 | this.callJs(method, [], jsReturnValueHandler) 269 | } 270 | 271 | callHandler(method: string, args?: any[], jsReturnValueHandler?: OnReturnValue) { 272 | this.callJs(method, args, jsReturnValueHandler) 273 | } 274 | 275 | callHandlerNoParam(method: string, jsReturnValueHandler?: OnReturnValue) { 276 | this.callJs(method, [], jsReturnValueHandler) 277 | } 278 | 279 | hasJavascriptMethod(method: string): Promise { 280 | if (this.checkIfDS2()) { 281 | return 282 | } 283 | return new Promise((resolve, reject) => { 284 | let handler: OnReturnValue = (has: boolean) => { 285 | resolve(has) 286 | } 287 | this.callHandler("_hasJavascriptMethod", [method], handler) 288 | }) 289 | } 290 | 291 | 292 | @JavaScriptInterface(false) 293 | private returnValue(param: string, value?: string) { 294 | if (this._isSupportDS2) { 295 | let id = Number.parseInt(param) 296 | if (id && this.handlerMap.has(id)) { 297 | let handler = this.handlerMap.get(id) 298 | handler(value) 299 | this.handlerMap.delete(id) 300 | } 301 | return 302 | } 303 | let p: { 304 | id?: number, 305 | complete?: boolean, 306 | data?: any 307 | } = JSON.parse(param) 308 | if (p.id && this.handlerMap.has(p.id)) { 309 | let handler = this.handlerMap.get(p.id) 310 | handler(p.data) 311 | if (p.complete) { 312 | this.handlerMap.delete(p.id) 313 | } 314 | 315 | } 316 | 317 | } 318 | 319 | @JavaScriptInterface(false) 320 | private dsinit(param: string) { 321 | 322 | } 323 | 324 | @JavaScriptInterface(false) 325 | private hasNativeMethod(param: string): boolean { 326 | if (this.checkIfDS2()) { 327 | return 328 | } 329 | const result: { 330 | code: number, 331 | msg: string 332 | } = { code: -1, msg: "" }; 333 | try { 334 | const p = JSON.parse(param) as NativeMethodParam 335 | const m = this.parseNamespace(p.name) 336 | let methodName = m[1] 337 | const obj = this.controller.javaScriptNamespaceInterfaces.get(m[0]) 338 | const method = Reflect.get(this.isNotEmpty(obj) ? obj : this, methodName); 339 | LogUtils.d(this + " " + methodName + " " + param + ' ' + obj) 340 | if (typeof method !== 'function') { 341 | const err = `call failed: ${methodName} is not a method or is not defined` 342 | result.msg = err 343 | } else { 344 | if (this.isNotEmpty(method)) { 345 | const decorator = Reflect.getMetadata(MetaData.METHOD_DECORATE, method) 346 | if (!decorator) { 347 | const err = `call failed: please add @JavaScriptInterface decorator in the ${methodName} method` 348 | result.msg = err 349 | } else { 350 | const async = Reflect.getMetadata(MetaData.ASYNC, method) ?? false 351 | result.code = (async && p.type === 'syn') || (!async && p.type === 'asyn') ? 1 : 0 352 | } 353 | } else { 354 | const err = `call failed: ${methodName} method does not exist` 355 | result.msg = err 356 | } 357 | } 358 | 359 | } catch (e) { 360 | result.msg = e 361 | } 362 | if (this.isNotEmpty(result.msg)) { 363 | LogUtils.e(result.msg) 364 | this.handlerError({ errMsg: result.msg } as CallResult, result.msg) 365 | } 366 | return result.code === 0 367 | } 368 | 369 | setClosePageListener(listener: OnCloseWindowListener) { 370 | this.jsClosePageListener = listener 371 | } 372 | 373 | @JavaScriptInterface(false) 374 | private closePage(param: any) { 375 | if (this.checkIfDS2()) { 376 | return 377 | } 378 | if (this.jsClosePageListener == null || this.jsClosePageListener() === true) { 379 | router.back() 380 | this.destroy() 381 | } 382 | } 383 | 384 | private checkIfDS2(): boolean { 385 | if (this._isSupportDS2) { 386 | ToastUtils.show('DS2.0脚本不支持该功能,请前端引入DS3.0脚本') 387 | return true 388 | } 389 | return false 390 | } 391 | 392 | destroy(){ 393 | this.interrupt = true 394 | this.jsClosePageListener = null 395 | this.handlerMap.clear() 396 | this._isSupportDS2 = false 397 | this.onErrorListener = null 398 | } 399 | 400 | injectDS2Js(){ 401 | if (this._isSupportDS2) { 402 | let script = 403 | "function getJsBridge(){window._dsf=window._dsf||{};return{call:function(b,a,c){\"function\"==typeof a&&(c=a,a={});if(\"function\"==typeof c){window.dscb=window.dscb||0;var d=\"dscb\"+window.dscb++;window[d]=c;a._dscbstub=d}a=JSON.stringify(a||{});return window._dswk?prompt(window._dswk+b,a):\"function\"==typeof _dsbridge?_dsbridge(b,a):_dsbridge.call(b,a)},register:function(b,a){\"object\"==typeof b?Object.assign(window._dsf,b):window._dsf[b]=a}}}dsBridge=getJsBridge();" 404 | this.controller.runJavaScript(script) 405 | } 406 | 407 | } 408 | 409 | } -------------------------------------------------------------------------------- /library/src/main/ets/core/Entity.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata" 2 | import { IWebViewControllerProxy } from './WebViewInterface' 3 | 4 | export interface JavaScriptProxy{ 5 | object: JsInterface, 6 | name: string, 7 | methodList: Array, 8 | controller: IWebViewControllerProxy 9 | } 10 | 11 | /** 12 | * 在js中统一用JavaScriptProxy#name来调用原生的call(x,x)方法 13 | */ 14 | export interface JsInterface { 15 | /** 16 | * @param methodName JS实际调用原生的方法 17 | * @param params JS携带过来的参数,有一个callbackID参数 18 | * @returns 19 | */ 20 | call(methodName: string, params: string): string 21 | } 22 | 23 | 24 | export interface CallResult { 25 | code: number, 26 | data?: Args, 27 | errMsg?: string, 28 | async?: boolean, 29 | } 30 | 31 | export interface Parameter { 32 | data?: string | Object, 33 | _dscbstub?: string, // JS callback函数名称 ,挂载到window对象中 34 | // _dscbstub 35 | } 36 | 37 | export interface CompleteHandler { 38 | /** 39 | * 只能使用一次 40 | * @param value 41 | */ 42 | complete(value: Args) 43 | /** 44 | * 可以使用多次 45 | * @param value 46 | */ 47 | setProgressData(value: Args) 48 | } 49 | 50 | export enum MetaData { 51 | METHOD_DECORATE = "bridge:JavaScriptInterface", 52 | ASYNC = "bridge:Async" 53 | } 54 | 55 | /** 56 | * @param asyncCall 57 | * @returns 58 | */ 59 | export function JavaScriptInterface(asyncCall: boolean = true): MethodDecorator { 60 | return (target, propertyKey, descriptor) => { 61 | Reflect.defineMetadata(MetaData.METHOD_DECORATE, propertyKey, descriptor.value !) 62 | Reflect.defineMetadata(MetaData.ASYNC, asyncCall, descriptor.value !) 63 | } 64 | } 65 | 66 | 67 | export interface NativeCallInfo { 68 | data: string, 69 | callbackId: number, 70 | method: string 71 | } 72 | 73 | export interface NativeMethodParam { 74 | name: string, 75 | type: 'syn' | 'asyn' | 'all' 76 | } 77 | 78 | export type OnReturnValue = (any) => void 79 | 80 | export type OnCloseWindowListener = () => boolean 81 | 82 | export type Args = number | string | boolean | Object 83 | 84 | export type OnErrorMessageListener = (string) => void 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /library/src/main/ets/core/WebViewControllerProxy.ets: -------------------------------------------------------------------------------- 1 | import webview from '@ohos.web.webview'; 2 | import { ToastUtils } from '../utils/ToastUtils'; 3 | import { BaseBridge } from './BaseBridge'; 4 | import { Args, JsInterface, OnCloseWindowListener, OnErrorMessageListener, OnReturnValue } from './Entity'; 5 | import { IWebViewControllerProxy, IBaseBridge } from './WebViewInterface'; 6 | 7 | 8 | export class WebViewControllerProxy implements IWebViewControllerProxy, IOhosWebviewController, IBaseBridge { 9 | private controller: webview.WebviewController 10 | private bridge: BaseBridge = new BaseBridge() 11 | javaScriptNamespaceInterfaces: Map = new Map(); 12 | 13 | constructor(controller: webview.WebviewController, obj?: object) { 14 | this.controller = controller 15 | this.bridge.setWebViewControllerProxy(this) 16 | this.addJavascriptObject(this.bridge, "_dsb") 17 | if (obj != null) { 18 | this.addJavascriptObject(obj) 19 | } 20 | } 21 | 22 | setGlobalErrorMessageListener(listener: OnErrorMessageListener): void { 23 | this.bridge.setGlobalErrorMessageListener(listener) 24 | } 25 | 26 | supportDS2(enable: boolean): void { 27 | this.bridge.supportDS2(enable) 28 | } 29 | 30 | injectDS2Js(): void { 31 | this.bridge.injectDS2Js() 32 | } 33 | 34 | 35 | addJavascriptObject = (obj: object, namespace: string = '') => { 36 | if (obj != null) { 37 | if (this.javaScriptNamespaceInterfaces.has(namespace)) { 38 | ToastUtils.show(namespace + "注入失败:一个组件内不能同时存在两个相同的命名空间名称") 39 | return 40 | } 41 | if (this.bridge.isSupportDS2 && namespace && namespace.length > 0) { 42 | ToastUtils.show(namespace + "注入失败:DSBridge2.0不支持命名空间") 43 | return 44 | } 45 | this.javaScriptNamespaceInterfaces.set(namespace, obj) 46 | } 47 | 48 | } 49 | 50 | hasJavascriptMethod(method: string): Promise { 51 | return this.bridge.hasJavascriptMethod(method) 52 | } 53 | 54 | 55 | 56 | callJs(method: string, args?: Args[], jsReturnValueHandler?: OnReturnValue) { 57 | this.bridge.callJs(method, args, jsReturnValueHandler) 58 | } 59 | 60 | 61 | callJsNoParam(method: string, jsReturnValueHandler?: OnReturnValue): void { 62 | this.bridge.callJsNoParam(method, jsReturnValueHandler) 63 | } 64 | 65 | callHandler(method: string, args?: Args[], jsReturnValueHandler?: OnReturnValue): void { 66 | this.bridge.callHandler(method, args, jsReturnValueHandler) 67 | 68 | } 69 | 70 | callHandlerNoParam(method: string, jsReturnValueHandler?: OnReturnValue): void { 71 | this.bridge.callHandlerNoParam(method, jsReturnValueHandler) 72 | } 73 | 74 | 75 | setClosePageListener(listener: OnCloseWindowListener) { 76 | this.bridge.setClosePageListener(listener) 77 | } 78 | 79 | 80 | destroy() { 81 | this.javaScriptNamespaceInterfaces.clear() 82 | this.bridge.destroy() 83 | } 84 | 85 | runJavaScript(script: string): Promise { 86 | return this.controller.runJavaScript(script); 87 | } 88 | 89 | registerJavaScriptProxy(object: object, name: string, methodList: string[]) { 90 | this.controller.registerJavaScriptProxy(object, name, methodList); 91 | this.refresh() 92 | } 93 | 94 | refresh() { 95 | this.controller.refresh() 96 | } 97 | 98 | getWebViewController(): webview.WebviewController { 99 | return this.controller 100 | } 101 | 102 | /** 103 | * @deprecated 104 | * @returns 105 | */ 106 | getJavaScriptProxy(): RealJavaScriptProxy { 107 | let javaScriptProxy: RealJavaScriptProxy = { 108 | object: this.bridge.javaScriptProxy().object, 109 | name: this.bridge.javaScriptProxy().name, 110 | methodList: this.bridge.javaScriptProxy().methodList, 111 | controller: this.controller 112 | } 113 | return javaScriptProxy 114 | } 115 | 116 | get javaScriptProxy(): RealJavaScriptProxy { 117 | return { 118 | object: this.bridge.javaScriptProxy().object, 119 | name: this.bridge.javaScriptProxy().name, 120 | methodList: this.bridge.javaScriptProxy().methodList, 121 | controller: this.controller 122 | } 123 | } 124 | 125 | 126 | static createController(obj?: object): WebViewControllerProxy { 127 | return new WebViewControllerProxy(new webview.WebviewController(), obj) 128 | } 129 | } 130 | 131 | /** 132 | * ohos相关的api 133 | */ 134 | export interface IOhosWebviewController { 135 | getWebViewController(): webview.WebviewController 136 | 137 | /** 138 | * @deprecated 139 | * @returns 140 | */ 141 | getJavaScriptProxy(): RealJavaScriptProxy 142 | 143 | get javaScriptProxy(): RealJavaScriptProxy 144 | } 145 | 146 | export interface RealJavaScriptProxy { 147 | object: JsInterface, 148 | name: string, 149 | methodList: Array, 150 | controller: webview.WebviewController 151 | } -------------------------------------------------------------------------------- /library/src/main/ets/core/WebViewInterface.ts: -------------------------------------------------------------------------------- 1 | import { Args, JavaScriptProxy, OnCloseWindowListener, OnReturnValue,OnErrorMessageListener } from './Entity'; 2 | 3 | /** 4 | * web组件api的代理接口,不能涉及到ohos的相关属性 5 | */ 6 | export interface IWebViewControllerProxy { 7 | 8 | readonly javaScriptNamespaceInterfaces: Map 9 | 10 | runJavaScript(script: string): Promise 11 | 12 | /** 13 | * @deprecated 14 | * @param object 15 | * @param name 16 | * @param methodList 17 | */ 18 | registerJavaScriptProxy(object: object, name: string, methodList: Array): void; 19 | 20 | refresh(): void; 21 | 22 | } 23 | 24 | 25 | 26 | export interface IBaseBridge { 27 | 28 | /** 29 | * 是否支持DSBridge2.0脚本 30 | * @param enable true:使用2.0脚本,false: 3.0脚本是默认的 31 | */ 32 | supportDS2(enable: boolean): void 33 | 34 | destroy(): void; 35 | 36 | callJs(method: string, args?: Args[], jsReturnValueHandler?: OnReturnValue): void 37 | 38 | callJsNoParam(method: string, jsReturnValueHandler?: OnReturnValue): void 39 | 40 | setClosePageListener(listener: OnCloseWindowListener): void 41 | 42 | hasJavascriptMethod(method: string): Promise 43 | 44 | callHandler(method: string, args?: Args[], jsReturnValueHandler?: OnReturnValue): void 45 | 46 | callHandlerNoParam(method: string, jsReturnValueHandler?: OnReturnValue): void 47 | 48 | setGlobalErrorMessageListener(listener: OnErrorMessageListener): void 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /library/src/main/ets/utils/LogUtils.ts: -------------------------------------------------------------------------------- 1 | import hilog from '@ohos.hilog' 2 | 3 | export class LogUtils { 4 | private static readonly LOG_TAG: string = "LogUtils" 5 | private static enable: boolean = true 6 | 7 | static logEnable(enabled: boolean) { 8 | this.enable = enabled 9 | } 10 | 11 | static dTag(tag: string, msg: any) { 12 | if (!this.enable) { 13 | return 14 | } 15 | hilog.info(0x0001, tag, msg) 16 | } 17 | 18 | 19 | static d(msg: any) { 20 | this.dTag(this.LOG_TAG, msg) 21 | } 22 | 23 | static eTag(tag: string, msg: any) { 24 | if (!this.enable) { 25 | return 26 | } 27 | hilog.error(0x0001, tag, msg) 28 | } 29 | 30 | static e(msg: any) { 31 | this.eTag(this.LOG_TAG, msg) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /library/src/main/ets/utils/ToastUtils.ts: -------------------------------------------------------------------------------- 1 | import { Prompt } from '@kit.ArkUI' 2 | 3 | export class ToastUtils { 4 | static show(msg: string) { 5 | Prompt.showToast({ message: msg }) 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /library/src/main/ets/wait/BaseSendable.ets: -------------------------------------------------------------------------------- 1 | /** 2 | * BaseSendable子类的属性只支持number、boolean、string、SendableClass 3 | * 4 | */ 5 | @Sendable 6 | export class BaseSendable { 7 | public enableLog: boolean = false 8 | public key: string = `key_${Date.now()}` 9 | public tag: string = "BaseSendable" 10 | private _isCompleted: boolean = false 11 | 12 | public get isCompleted(): boolean { 13 | return this._isCompleted 14 | } 15 | 16 | 17 | /** 18 | * 不能手动调用complete()、run()方法 19 | */ 20 | async complete() { 21 | this._isCompleted = true 22 | } 23 | 24 | async run(): Promise { 25 | } 26 | } -------------------------------------------------------------------------------- /library/src/main/ets/wait/TaskWait.ets: -------------------------------------------------------------------------------- 1 | import { LogUtils } from '../utils/LogUtils' 2 | import { BaseSendable } from './BaseSendable' 3 | import { taskpool } from '@kit.ArkTS' 4 | 5 | 6 | @Concurrent 7 | async function run(sendable: BaseSendable) { 8 | const currentTime = Date.now() 9 | await sendable.run() 10 | await sendable.complete() 11 | if (sendable.enableLog) { 12 | LogUtils.d(`${sendable.tag} task time: ${Date.now() - currentTime}`) 13 | } 14 | 15 | } 16 | 17 | /** 18 | * 通过taskWait方法可以在主线程同步方法内执行串行异步并发任务,主线程会同步等待异步结果 19 | * 不建议做耗时过长的任务,当任务超过3s,无论是否执行完毕都会结束,数据可能会丢失 20 | * 为何是3s呢,当主线程阻塞超过3s时,系统会发生anr无响应异常,闪退。 21 | * @param args 22 | * @returns 23 | */ 24 | export function taskWait(args: BaseSendable): T { 25 | const task: taskpool.Task = new taskpool.Task(run, args) 26 | taskpool.execute(task) 27 | let currentTime = Date.now() 28 | while (true) { 29 | if (Date.now() - currentTime < 3000) { 30 | if (args.isCompleted) { 31 | break 32 | } 33 | } else { 34 | break 35 | } 36 | } 37 | if (!args.isCompleted) { 38 | taskpool.getTaskPoolInfo().taskInfos.forEach((v) => { 39 | if (args.enableLog) { 40 | LogUtils.d(`${args.tag} task info: ${JSON.stringify(v)}`) 41 | } 42 | try { 43 | taskpool.cancel(task) 44 | } catch (e) { 45 | LogUtils.e(JSON.stringify(e)) 46 | } 47 | }) 48 | } 49 | if (args.enableLog) { 50 | LogUtils.d(`${args.tag} task result: ${JSON.stringify(args)} time: ${Date.now() - currentTime}`) 51 | } 52 | return args as T 53 | } -------------------------------------------------------------------------------- /library/src/main/module.json5: -------------------------------------------------------------------------------- 1 | { 2 | "module": { 3 | "name": "library", 4 | "type": "har", 5 | "deviceTypes": [ 6 | "default", 7 | "tablet" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /library/src/main/resources/base/element/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": [ 3 | { 4 | "name": "page_show", 5 | "value": "page from npm package" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /libs/dsbridge.har: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/751496032/DSBridge-HarmonyOS/7515ee4037c91959c4a7e2244802cf67a3e3a7c3/libs/dsbridge.har -------------------------------------------------------------------------------- /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 | "@hzw/zrouter@^1.0.7": "@hzw/zrouter@1.1.0", 9 | "@ohos/hypium@1.0.6": "@ohos/hypium@1.0.6", 10 | "reflect-metadata@^0.1.13": "reflect-metadata@0.1.13" 11 | }, 12 | "packages": { 13 | "@hzw/zrouter@1.1.0": { 14 | "name": "@hzw/zrouter", 15 | "version": "1.1.0", 16 | "integrity": "sha512-nYO+MiOSxnEBOncc+m2CMRzEw2csBbAbyeArtzUgjfHW4BLKD6cDVZzK9kjYMCVf7pk0GxQiONUL3Ff0zGiGrg==", 17 | "resolved": "https://ohpm.openharmony.cn/ohpm/@hzw/zrouter/-/zrouter-1.1.0.har", 18 | "registryType": "ohpm", 19 | "dependencies": { 20 | "reflect-metadata": "^0.1.13" 21 | } 22 | }, 23 | "@ohos/hypium@1.0.6": { 24 | "name": "@ohos/hypium", 25 | "version": "1.0.6", 26 | "integrity": "sha512-bb3DWeWhYrFqj9mPFV3yZQpkm36kbcK+YYaeY9g292QKSjOdmhEIQR2ULPvyMsgSR4usOBf5nnYrDmaCCXirgQ==", 27 | "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.6.tgz", 28 | "shasum": "3f5fed65372633233264b3447705b0831dfe7ea1", 29 | "registryType": "ohpm" 30 | }, 31 | "reflect-metadata@0.1.13": { 32 | "name": "reflect-metadata", 33 | "version": "0.1.13", 34 | "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", 35 | "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.1.13.tgz", 36 | "shasum": "67ae3ca57c972a2aa1642b10fe363fe32d49dc08", 37 | "registryType": "ohpm" 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /oh-package.json5: -------------------------------------------------------------------------------- 1 | { 2 | "modelVersion": "5.0.0", 3 | "license": "", 4 | "devDependencies": { 5 | "@ohos/hypium": "1.0.6" 6 | }, 7 | "author": "HZWei", 8 | "name": "dsbrige_demo", 9 | "description": "Please describe the basic information.", 10 | "main": "", 11 | "version": "1.0.0", 12 | "dependencies": { 13 | "@hzw/zrouter": "^1.0.7" 14 | }, 15 | "dynamicDependencies": {} 16 | } --------------------------------------------------------------------------------