1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import webView from '@ohos.web.webview'; 17import router from '@ohos.router'; 18import deviceInfo from '@ohos.deviceInfo'; 19import common from '@ohos.app.ability.common'; 20import geoLocationManager from '@ohos.geoLocationManager'; 21import bundleManager from '@ohos.bundle.bundleManager'; 22import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; 23import connection from '@ohos.net.connection'; 24import request from '@ohos.request'; 25import fs from '@ohos.file.fs'; 26import util from '@ohos.util'; 27import photoAccessHelper from '@ohos.file.photoAccessHelper'; 28import { filePreview } from '@kit.PreviewKit'; 29import fileUri from '@ohos.file.fileuri'; 30import picker from '@ohos.multimedia.cameraPicker'; 31import filePicker from '@ohos.file.picker'; 32import { BusinessError } from '@ohos.base'; 33import { call } from '@kit.TelephonyKit'; 34 35let atomicBasicEngine: ESObject | null = null; 36 37/** 38 * 初始化加载atomicbasicengine 39 */ 40function loadAtomicBasicEngine(): void { 41 try { 42 import('@hms.atomicservicedistribution.atomicbasicengine').then((ns: ESObject) => { 43 console.log('AtomicServiceWeb loadAtomicBasicEngine success'); 44 atomicBasicEngine = ns; 45 }).catch((err: BusinessError) => { 46 console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + err.message); 47 }); 48 } catch (err) { 49 console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + err.message); 50 } 51} 52 53loadAtomicBasicEngine(); 54 55class AsError { 56 public code: number; 57 public message: string; 58 59 constructor(code: number, message: string) { 60 this.code = code; 61 this.message = message; 62 } 63} 64 65class JsApiConfig { 66 public apiName: string; 67 public minVersion: string; 68 public maxVersion: string; 69 public requiredFieldNames?: string[]; 70 71 constructor(apiName: string, minVersion: string, maxVersion: string, requiredFieldNames?: string[]) { 72 this.apiName = apiName; 73 this.minVersion = minVersion; 74 this.maxVersion = maxVersion; 75 this.requiredFieldNames = requiredFieldNames; 76 } 77} 78 79const LOG_ENABLE: boolean = true; 80const LOG_PREFIX: string = '[AtomicServiceWebLog]'; 81const UPLOAD_IMAGE_CACHE_DIR: string = '/cache/'; 82const JAVA_SCRIPT_PROXY_OBJECT_NAME: string = 'atomicServiceProxy'; 83const JAVA_SCRIPT_PROXY_API_NAME_LIST: string[] = ['invokeJsApi']; 84const ATOMIC_SERVICE_JS_API_MAP = new Map<string, JsApiConfig>(); 85const registerJsApi = (apiNameAlias: string, apiName: string, minVersion: string, maxVersion: string, 86 requiredFieldNames: string[]): void => { 87 ATOMIC_SERVICE_JS_API_MAP.set(apiNameAlias, new JsApiConfig(apiName, minVersion, maxVersion, requiredFieldNames)); 88}; 89const MAX_VERSION = '99.99.99'; 90const ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION = '1.0.0'; 91const PERMISSION_APPROXIMATELY_LOCATION: Permissions = 'ohos.permission.APPROXIMATELY_LOCATION'; 92const TEL_PROTOCOL: string = 'tel:'; 93const MAILTO_PROTOCOL: string = 'mailto:'; 94const WANT_ACTION_SEND_TO_DATA: string = 'ohos.want.action.sendToData'; 95const RESOURCE_RAWFILE: string = 'resource://rawfile'; 96const TYPE_AS_WEB: string = 'ASWeb'; 97const WEB_PERMISSIONS: Record<string, string> = { 98 'TYPE_VIDEO_CAPTURE': 'ohos.permission.CAMERA', 99 'TYPE_AUDIO_CAPTURE': 'ohos.permission.MICROPHONE' 100}; 101 102const SYSTEM_INTERNAL_ERROR: AsError = new AsError(500, 'System internal error.'); 103const JS_API_INVALID_INVOKE_ERROR: AsError = new AsError(200001, 'Invalid invoke.'); 104const PARAM_REQUIRED_ERROR_CODE: number = 200002; 105const PARAM_NUMBER_POSITIVE_ERROR_CODE: number = 200003; 106const ROUTER_PARAM_MODE_INVALID_ERROR: AsError = new AsError(200004, 'Param mode is invalid.'); 107const BACK_URL_NOT_EXIST_OR_OPENED_ERROR: AsError = new AsError(200005, 'Url is not exist or opened, can not be back.'); 108const NAV_PATH_STACK_NOT_EXIST_ERROR_CODE: number = 200006; 109const POP_PATH_NAME_NOT_EXIST_ERROR: AsError = new AsError(200007, 'Name is not exist or opened, can not be pop.'); 110const POP_PATH_PARAM_INDEX_INVALID_ERROR: AsError = new AsError(200008, 'Param index is invalid.'); 111const POP_PATH_INDEX_OUT_OF_RANGE_ERROR: AsError = new AsError(200009, 'The Index is out of range.'); 112const UPLOAD_IMAGE_FILES_REQUIRED_ERROR: AsError = new AsError(200010, 'Param files is required.'); 113const UPLOAD_IMAGE_FILE_NOT_EXIST_ERROR_CODE: number = 200011; 114const UPLOAD_IMAGE_FILES_URI_REQUIRED_ERROR: AsError = new AsError(200012, 'Param uri of files is required.'); 115const UPLOAD_FILE_ERROR: AsError = new AsError(200013, 'Upload file error.'); 116const IMAGE_CAN_NOT_PREVIEW_ERROR: AsError = new AsError(200014, 'The filePath can not preview.'); 117const NETWORK_NO_ACTIVE_ERROR: AsError = new AsError(200015, 'The network is not active.'); 118const PERMISSION_LOCATION_USER_REFUSED_ERROR: number = 200016; 119 120registerJsApi('router.pushUrl', 'pushUrl', '1.0.0', MAX_VERSION, ['url']); 121registerJsApi('router.replaceUrl', 'replaceUrl', '1.0.0', MAX_VERSION, ['url']); 122registerJsApi('router.back', 'backUrl', '1.0.0', MAX_VERSION, []); 123registerJsApi('router.clear', 'clearUrl', '1.0.0', MAX_VERSION, []); 124registerJsApi('navPathStack.pushPath', 'pushPath', '1.0.0', MAX_VERSION, ['name']); 125registerJsApi('navPathStack.replacePath', 'replacePath', '1.0.0', MAX_VERSION, ['name']); 126registerJsApi('navPathStack.pop', 'popPath', '1.0.0', MAX_VERSION, []); 127registerJsApi('navPathStack.clear', 'clearPath', '1.0.0', MAX_VERSION, []); 128registerJsApi('asWeb.postMessage', 'postMessage', '1.0.0', MAX_VERSION, ['data']); 129registerJsApi('asWeb.getEnv', 'getEnv', '1.0.0', MAX_VERSION, []); 130registerJsApi('asWeb.checkJsApi', 'checkJsApi', '1.0.0', MAX_VERSION, ['jsApiList']); 131registerJsApi('cameraPicker.pick', 'pickCamera', '1.0.0', MAX_VERSION, ['mediaTypes', 'cameraPosition']); 132registerJsApi('photoViewPicker.select', 'selectPhoto', '1.0.0', MAX_VERSION, []); 133registerJsApi('filePreview.openPreview', 'openPreview', '1.0.0', MAX_VERSION, ['uri']); 134registerJsApi('request.uploadFile', 'uploadFile', '1.0.0', MAX_VERSION, ['url', 'files']); 135registerJsApi('request.downloadFile', 'downloadFile', '1.0.0', MAX_VERSION, ['url']); 136registerJsApi('connection.getNetworkType', 'getNetworkType', '1.0.0', MAX_VERSION, []); 137registerJsApi('location.getLocation', 'getLocation', '1.0.0', MAX_VERSION, []); 138 139@Component 140export struct AtomicServiceWeb { 141 public src: ResourceStr | undefined = undefined; 142 public navPathStack?: NavPathStack; 143 @Prop mixedMode?: MixedMode; 144 @Prop darkMode?: WebDarkMode; 145 @Prop forceDarkAccess?: boolean; 146 @Prop nestedScroll?: NestedScrollOptions | NestedScrollOptionsExt; 147 @ObjectLink controller: AtomicServiceWebController; 148 public onMessage?: Callback<OnMessageEvent> = () => { 149 }; 150 public onErrorReceive?: Callback<OnErrorReceiveEvent> = () => { 151 }; 152 public onHttpErrorReceive?: Callback<OnHttpErrorReceiveEvent> = () => { 153 }; 154 public onPageBegin?: Callback<OnPageBeginEvent> = () => { 155 }; 156 public onPageEnd?: Callback<OnPageEndEvent> = () => { 157 }; 158 public onProgressChange?: Callback<OnProgressChangeEvent> = () => { 159 }; 160 public onControllerAttached?: VoidCallback; 161 public onLoadIntercept?: Callback<OnLoadInterceptEvent, boolean>; 162 private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; 163 private webViewController: webView.WebviewController = new webView.WebviewController(); 164 private schemeHandler: webView.WebSchemeHandler = new webView.WebSchemeHandler(); 165 private atomicService?: AtomicService; 166 private atomicServiceProxy?: AtomicServiceProxy; 167 168 aboutToAppear(): void { 169 if (!this.atomicService) { 170 this.atomicService = new AtomicServiceApi(this.context, this.navPathStack, this.onMessage); 171 this.atomicServiceProxy = new AtomicServiceProxy(this.atomicService); 172 } 173 174 try { 175 let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync( 176 bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); 177 if (bundleInfo?.appInfo?.appProvisionType === 'debug') { 178 console.log(`AtomicServiceWeb setWebDebuggingAccess`); 179 webView.WebviewController.setWebDebuggingAccess(true); 180 } 181 } catch (err) { 182 console.error(`AtomicServiceWeb set Web Debug Mode failed, code is ${err.code}, message is ${err.message}`); 183 } 184 185 this.initDomainCheckLog(); 186 } 187 188 aboutToDisappear(): void { 189 this.atomicService?.notifyMessage(); 190 } 191 192 build() { 193 Web({ src: this.src, controller: this.webViewController }) 194 .zoomAccess(false) 195 .allowWindowOpenMethod(false) 196 .domStorageAccess(true) 197 .layoutMode(WebLayoutMode.NONE) 198 .mixedMode(this.mixedMode) 199 .darkMode(this.darkMode) 200 .forceDarkAccess(this.forceDarkAccess) 201 .nestedScroll(this.nestedScroll) 202 .onErrorReceive((event: OnErrorReceiveEvent) => this.onCommonCallBack('onErrorReceive', event, 203 this.onErrorReceive)) 204 .onHttpErrorReceive((event: OnHttpErrorReceiveEvent) => this.onCommonCallBack('onHttpErrorReceive', event, 205 this.onHttpErrorReceive)) 206 .onPageBegin((event: OnPageBeginEvent) => this.onCommonCallBack('onPageBegin', event, this.onPageBegin)) 207 .onPageEnd((event: OnPageEndEvent) => this.onCommonCallBack('onPageEnd', event, this.onPageEnd)) 208 .onProgressChange((event: OnProgressChangeEvent) => this.onCommonCallBack('onProgressChange', event, 209 this.onProgressChange)) 210 .onControllerAttached(() => { 211 this.registerJavaScriptProxy(); 212 this.schemeHandler.onRequestStart((request: webView.WebSchemeHandlerRequest) => { 213 return !this.interceptUrl(request.getRequestUrl(), request.isMainFrame(), request.getRequestResourceType()); 214 }); 215 this.webViewController.setWebSchemeHandler('http', this.schemeHandler); 216 this.webViewController.setWebSchemeHandler('https', this.schemeHandler); 217 this.initAtomicServiceWebController(); 218 if (this.onControllerAttached) { 219 try { 220 this.onControllerAttached(); 221 } catch (error) { 222 console.error(`AtomicServiceWeb onControllerAttached failed, code is ${error.code}, message is ${error.message}`); 223 } 224 } 225 }) 226 .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => { 227 return !this.interceptOverrideUrlLoading(webResourceRequest.getRequestUrl()); 228 }) 229 .onLoadIntercept(event => { 230 let checkResult = !this.checkUrl(event.data.getRequestUrl()); 231 if (!checkResult && this.onLoadIntercept) { 232 try { 233 return this.onLoadIntercept(event); 234 } catch (error) { 235 console.error(`AtomicServiceWeb onLoadIntercept failed, code is ${error.code}, message is ${error.message}`); 236 return true; 237 } 238 } 239 return checkResult; 240 }) 241 .onPermissionRequest((event: OnPermissionRequestEvent) => { 242 this.handleOnPermissionRequest(event); 243 }) 244 } 245 246 onCommonCallBack<T>(method: string, event: T, callback?: (event: T) => void): void { 247 try { 248 callback && callback(event); 249 } catch (error) { 250 console.error(`AtomicServiceWeb ${method} failed, code is ${error.code}, message is ${error.message}`); 251 } 252 } 253 254 registerJavaScriptProxy(): void { 255 try { 256 this.webViewController.registerJavaScriptProxy(this.atomicServiceProxy, JAVA_SCRIPT_PROXY_OBJECT_NAME, 257 JAVA_SCRIPT_PROXY_API_NAME_LIST); 258 } catch (error) { 259 let e: BusinessError = error as BusinessError; 260 console.error(`AtomicServiceWeb registerJavaScriptProxy failed, code is ${e.code}, message is ${e.message}`); 261 } 262 } 263 264 initAtomicServiceWebController(): void { 265 if (!this.controller) { 266 return; 267 } 268 this.controller.setWebviewController(this.webViewController); 269 } 270 271 cutUrl(url: string): string { 272 if (url) { 273 let index: number = url.indexOf('?'); 274 if (index > -1) { 275 return url.substring(0, index); 276 } 277 } 278 return url; 279 } 280 281 checkUrl(url: string): boolean { 282 if (!url) { 283 return false; 284 } 285 if (url.startsWith('resource://rawfile')) { 286 return true; 287 } 288 url = this.cutUrl(url); 289 return true; 290 } 291 292 /** 293 * 初始化域名校验日志 294 */ 295 initDomainCheckLog(): void { 296 try { 297 let appId: string | null = this.getAppId(); 298 let checkDomainBlockListAvailable: boolean = this.isCheckDomainBlockListAvailable(); 299 console.debug('AtomicServiceWeb initDomainCheckLog appId=' + appId + ' checkDomainBlockListAvailable=' + 300 checkDomainBlockListAvailable); 301 } catch (err) { 302 console.error('AtomicServiceWeb initDomainCheckLog error, message: ' + err.message); 303 } 304 } 305 306 /** 307 * 获取appid 308 */ 309 getAppId(): string | null { 310 let bundleName: string = this.context.abilityInfo.bundleName; 311 if (!bundleName) { 312 return null; 313 } 314 let strArray: string[] = bundleName.split('.'); 315 if (!strArray || strArray.length <= 0) { 316 return null; 317 } 318 return strArray[strArray.length - 1]; 319 } 320 321 /** 322 * 是否主页面或iframe页面请求 323 */ 324 isMainPageOrIframeRequest(isMainFrame: boolean, requestResourceType: number): boolean { 325 if (isMainFrame) { 326 return true; 327 } 328 if (requestResourceType === webView.WebResourceType.MAIN_FRAME || 329 requestResourceType === webView.WebResourceType.SUB_FRAME) { 330 return true; 331 } 332 return false; 333 } 334 335 /** 336 * 检查checkDomainBlockList接口是否可用 337 */ 338 isCheckDomainBlockListAvailable(): boolean { 339 if (!atomicBasicEngine || !atomicBasicEngine.default) { 340 return false; 341 } 342 return typeof atomicBasicEngine.default.checkDomainBlockList === 'function'; 343 } 344 345 /** 346 * 拦截url请求 347 */ 348 interceptUrl(url: string, isMainFrame: boolean, requestResourceType: number): boolean { 349 if (!url) { 350 return false; 351 } 352 if (url.startsWith(RESOURCE_RAWFILE)) { 353 return true; 354 } 355 // 主页面或iframe页面,走域名白名单校验 356 if (this.isMainPageOrIframeRequest(isMainFrame, requestResourceType)) { 357 return this.checkUrl(url); 358 } 359 // 非主页面或iframe页面,atomicBasicEngine可用,走域名黑名单校验 360 if (this.isCheckDomainBlockListAvailable()) { 361 return this.checkUrlNew(url); 362 } 363 // 非主页面或iframe页面,atomicBasicEngine不可用,放通管控 364 return true; 365 } 366 367 /** 368 * 域名黑名单校验 369 */ 370 checkUrlNew(url: string): boolean { 371 let appId: string | null = this.getAppId(); 372 if (!appId) { 373 console.error('AtomicServiceWeb checkUrlNew error, appId is invalid'); 374 return false; 375 } 376 try { 377 let shortUrl: string = this.cutUrl(url); 378 let isInDomainBlockList: boolean = atomicBasicEngine.default.checkDomainBlockList(shortUrl, appId); 379 console.debug(`AtomicServiceWeb checkUrlNew ret=${!isInDomainBlockList} url=${shortUrl}`); 380 return !isInDomainBlockList; 381 } catch (err) { 382 console.error(`AtomicServiceWeb checkUrlNew error, code: ${err.code}, message: ${err.message}`); 383 // 黑名单接口调用失败,放通管控 384 return true; 385 } 386 } 387 388 /** 389 * 拦截url跳转 390 */ 391 interceptOverrideUrlLoading(url: string): boolean { 392 if (!url) { 393 return false; 394 } 395 // 处理tel:协议 396 if (url.startsWith(TEL_PROTOCOL)) { 397 this.openMakeCall(url); 398 return false; 399 } 400 // 处理mailto:协议 401 if (url.startsWith(MAILTO_PROTOCOL)) { 402 this.openSendMail(url); 403 return false; 404 } 405 return this.checkUrl(url); 406 } 407 408 /** 409 * 拉起打电话 410 */ 411 openMakeCall(url: string): void { 412 if (!url || !url.startsWith(TEL_PROTOCOL)) { 413 return; 414 } 415 try { 416 let phoneNumber: string = url.substring(TEL_PROTOCOL.length); 417 call.makeCall(phoneNumber).catch((err: BusinessError) => { 418 console.error(`AtomicServiceWeb openMakeCall error, code: ${err.code}, message: ${err.message}`); 419 }); 420 } catch (err) { 421 console.error(`AtomicServiceWeb openMakeCall error, code: ${err.code}, message: ${err.message}`); 422 } 423 } 424 425 /** 426 * 拉起发邮件 427 */ 428 openSendMail(url: string): void { 429 if (!url || !url.startsWith(MAILTO_PROTOCOL)) { 430 return; 431 } 432 try { 433 this.context.startAbility({ 434 action: WANT_ACTION_SEND_TO_DATA, 435 uri: url 436 }).catch((err: BusinessError) => { 437 console.error(`AtomicServiceWeb openSendMail error, code: ${err.code}, message: ${err.message}`); 438 }); 439 } catch (err) { 440 console.error(`AtomicServiceWeb openSendMail error, code: ${err.code}, message: ${err.message}`); 441 } 442 } 443 444 /** 445 * 处理onPermissionRequest回调 446 */ 447 private handleOnPermissionRequest(event: OnPermissionRequestEvent): void { 448 if (this.checkPermissionRequest(event)) { 449 event.request.grant(event.request.getAccessibleResource()); 450 } else { 451 event.request.deny(); 452 } 453 } 454 455 /** 456 * onPermissionRequest权限校验 457 */ 458 private checkPermissionRequest(event: OnPermissionRequestEvent): boolean { 459 let accessibleResource: string[] = event.request.getAccessibleResource(); 460 if (!accessibleResource || accessibleResource.length <= 0) { 461 return false; 462 } 463 let appId: string | null = this.getAppId(); 464 if (!appId) { 465 console.error('AtomicServiceWeb checkPermissionRequest error, appId is invalid'); 466 return false; 467 } 468 for (let resource of accessibleResource) { 469 let permission: string = WEB_PERMISSIONS[resource]; 470 if (!permission) { 471 console.error('AtomicServiceWeb checkPermissionRequest error, permission is not support'); 472 return false; 473 } 474 if (!this.isPermissionUserGranted(permission)) { 475 return false; 476 } 477 if (!this.isPermissionWhiteListAllow(appId, permission)) { 478 return false; 479 } 480 } 481 return true; 482 } 483 484 /** 485 * 检查用户是否授予权限 486 */ 487 private isPermissionUserGranted(permission: string): boolean { 488 try { 489 let bundleInfo: bundleManager.BundleInfo = 490 bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); 491 if (!bundleInfo?.appInfo?.accessTokenId) { 492 return false; 493 } 494 let tokenId: number = bundleInfo.appInfo.accessTokenId; 495 let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.createAtManager() 496 .checkAccessTokenSync(tokenId, permission as Permissions); 497 if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 498 console.error(`AtomicServiceWeb isPermissionGranted permission ${permission} is not grant`); 499 return false; 500 } 501 return true; 502 } catch (err) { 503 console.error(`AtomicServiceWeb isPermissionGranted error, code: ${err.code}, message: ${err.message}`); 504 return false; 505 } 506 } 507 508 /** 509 * 检查权限白名单是否通过 510 */ 511 private isPermissionWhiteListAllow(appId: string, permission: string): boolean { 512 if (!atomicBasicEngine || !atomicBasicEngine.default || 513 typeof atomicBasicEngine.default.checkAtomicServiceAllow !== 'function') { 514 console.error('AtomicServiceWeb isPermissionRequestAllow error, checkAtomicServiceAllow is not available'); 515 return false; 516 } 517 try { 518 let isAllow: boolean = atomicBasicEngine.default.checkAtomicServiceAllow(appId, permission, TYPE_AS_WEB); 519 console.debug(`AtomicServiceWeb isPermissionRequestAllow ret=${isAllow} permission=${permission}`); 520 return isAllow; 521 } catch (err) { 522 console.error(`AtomicServiceWeb isPermissionRequestAllow error, code: ${err.code}, message: ${err.message}`); 523 return false; 524 } 525 } 526} 527 528@Observed 529export class AtomicServiceWebController { 530 private webViewController?: webView.WebviewController; 531 532 setWebviewController(webViewController: webView.WebviewController): void { 533 this.webViewController = webViewController; 534 } 535 536 checkWebviewController(): void { 537 if (!this.webViewController) { 538 const error: BusinessError<string> = { 539 name: '', 540 message: 'Init error. The AtomicServiceWebController must be associated with a AtomicServiceWeb component.', 541 code: 17100001, 542 } 543 throw error as Error; 544 } 545 } 546 547 getUserAgent(): string | undefined { 548 this.checkWebviewController(); 549 return this.webViewController?.getUserAgent(); 550 } 551 552 getCustomUserAgent(): string | undefined { 553 this.checkWebviewController(); 554 return this.webViewController?.getCustomUserAgent(); 555 } 556 557 setCustomUserAgent(userAgent: string): void { 558 this.checkWebviewController(); 559 this.webViewController?.setCustomUserAgent(userAgent); 560 } 561 562 accessForward(): boolean | undefined { 563 this.checkWebviewController(); 564 return this.webViewController?.accessForward(); 565 } 566 567 accessBackward(): boolean | undefined { 568 this.checkWebviewController(); 569 return this.webViewController?.accessBackward(); 570 } 571 572 accessStep(step: number): boolean | undefined { 573 this.checkWebviewController(); 574 return this.webViewController?.accessStep(step); 575 } 576 577 forward(): void { 578 this.checkWebviewController(); 579 this.webViewController?.forward(); 580 } 581 582 backward(): void { 583 this.checkWebviewController(); 584 this.webViewController?.backward(); 585 } 586 587 refresh(): void { 588 this.checkWebviewController(); 589 this.webViewController?.refresh(); 590 } 591 592 loadUrl(url: string | Resource, headers?: Array<WebHeader>): void { 593 this.checkWebviewController(); 594 if (headers) { 595 this.webViewController?.loadUrl(url, headers); 596 } else { 597 this.webViewController?.loadUrl(url); 598 } 599 } 600} 601 602class AtomicServiceProxy { 603 private atomicService: AtomicService; 604 605 constructor(atomicService: AtomicService) { 606 this.atomicService = atomicService; 607 } 608 609 invokeJsApi<T>(apiNameAlias: string, options: BaseOptions<T>): void { 610 try { 611 options = options || {}; 612 if (!apiNameAlias || !ATOMIC_SERVICE_JS_API_MAP.has(apiNameAlias)) { 613 this.atomicService.errorWithCodeAndMsg(JS_API_INVALID_INVOKE_ERROR, options); 614 return; 615 } 616 let jsApiConfig: JsApiConfig | undefined = ATOMIC_SERVICE_JS_API_MAP.get(apiNameAlias); 617 if (!this.atomicService.checkRequiredFieldInOptions(jsApiConfig, options)) { 618 return; 619 } 620 let atomicService: object = this.atomicService; 621 atomicService[jsApiConfig?.apiName as string](options); 622 } catch (err) { 623 this.atomicService.error(err, options); 624 } 625 } 626} 627 628class AtomicService { 629 protected context: common.UIAbilityContext; 630 protected navPathStack?: NavPathStack; 631 protected messageDataList: object[] = []; 632 protected onMessage: (event: OnMessageEvent) => void = () => { 633 }; 634 635 constructor(context: common.UIAbilityContext, navPathStack?: NavPathStack, 636 onMessage?: (event: OnMessageEvent) => void) { 637 this.context = context; 638 this.navPathStack = navPathStack; 639 this.onMessage = onMessage ? onMessage : this.onMessage; 640 } 641 642 success<T>(res: T, options: BaseOptions<T>): void { 643 try { 644 options?.callback && options?.callback(undefined, res); 645 } catch (err) { 646 this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`); 647 } 648 } 649 650 error<T>(err: BusinessError, options: BaseOptions<T>,): void { 651 try { 652 options?.callback && options?.callback(new AsError(err.code ? err.code : SYSTEM_INTERNAL_ERROR.code, 653 err.message ? err.message : SYSTEM_INTERNAL_ERROR.message)); 654 } catch (err) { 655 this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`); 656 } 657 } 658 659 errorWithCodeAndMsg<T>(error: AsError, options: BaseOptions<T>): void { 660 try { 661 options?.callback && options?.callback(error); 662 } catch (err) { 663 this.consoleError(`callback error, code is ${err.code}, message is ${err.message}`); 664 } 665 } 666 667 consoleLog(msg: string): void { 668 if (LOG_ENABLE) { 669 console.log(`${LOG_PREFIX} ${msg}`); 670 } 671 } 672 673 consoleError(msg: string): void { 674 if (LOG_ENABLE) { 675 console.error(`${LOG_PREFIX} ${msg}`); 676 } 677 } 678 679 logOptions<T>(name: string, options: BaseOptions<T>): void { 680 this.consoleLog(`${name} options=${JSON.stringify(options)}`); 681 } 682 683 checkParamRequired<V, T>(paramKey: string, paramValue: V, options: BaseOptions<T>): boolean { 684 if (paramValue === undefined || paramValue === null || paramValue === '') { 685 this.errorWithCodeAndMsg(new AsError(PARAM_REQUIRED_ERROR_CODE, `Param ${paramKey} is required.`), options); 686 return false; 687 } 688 return true; 689 } 690 691 checkNumberParamPositive<T>(paramKey: string, paramValue: number, options: BaseOptions<T>): boolean { 692 if (paramValue <= 0) { 693 this.errorWithCodeAndMsg(new AsError(PARAM_NUMBER_POSITIVE_ERROR_CODE, 694 `Param ${paramKey} must be a positive number.`), options); 695 return false; 696 } 697 return true; 698 } 699 700 checkRequiredFieldInOptions<T>(jsApiConfig: JsApiConfig | undefined, options: BaseOptions<T>): boolean { 701 if (!jsApiConfig) { 702 return false; 703 } 704 if (!jsApiConfig.requiredFieldNames) { 705 return true; 706 } 707 let obj: object = options; 708 for (let i = 0; i < jsApiConfig.requiredFieldNames.length; i++) { 709 let fieldName: string = jsApiConfig.requiredFieldNames[i]; 710 if (!this.checkParamRequired(fieldName, obj[fieldName], options)) { 711 return false; 712 } 713 } 714 return true; 715 } 716 717 checkRouterMode<T>(mode: string | undefined, options: BaseOptions<T>): boolean { 718 if (!mode || mode === 'Single' || mode === 'Standard') { 719 return true; 720 } 721 this.errorWithCodeAndMsg(ROUTER_PARAM_MODE_INVALID_ERROR, options); 722 return false; 723 } 724 725 parseRouterMode(routerMode?: string): router.RouterMode { 726 return routerMode === 'Single' ? router.RouterMode.Single : router.RouterMode.Standard; 727 } 728 729 getRouterIndexByDelta(delta: number): number { 730 let length: number = Number.parseInt(router.getLength()); 731 for (let i = length; i > 0; i--) { 732 let state = router.getStateByIndex(i); 733 if (state?.name && delta-- == 0) { 734 return i; 735 } 736 } 737 return 1; 738 } 739 740 checkBackUrlExists<T>(url: string, options: BaseOptions<T>): boolean { 741 let length: number = Number.parseInt(router.getLength()); 742 for (let i = length; i > 0; i--) { 743 let state = router.getStateByIndex(i); 744 if (state?.name) { 745 let stateUrl: string = state?.path + state?.name; 746 if (stateUrl === url) { 747 return true; 748 } 749 } 750 } 751 this.errorWithCodeAndMsg(BACK_URL_NOT_EXIST_OR_OPENED_ERROR, options); 752 return false; 753 } 754 755 checkNavPathStack<T>(apiName: string, options: BaseOptions<T>): boolean { 756 if (!this.navPathStack) { 757 this.errorWithCodeAndMsg(new AsError(NAV_PATH_STACK_NOT_EXIST_ERROR_CODE, 758 `Current page is not NavDestination, not support ${apiName}().`), options); 759 return false; 760 } 761 return true; 762 } 763 764 getNavPathIndexByDelta(delta: number): number { 765 let pathStack: string[] | undefined = this.navPathStack?.getAllPathName(); 766 if (!pathStack || pathStack.length == 0) { 767 return -1; 768 } 769 return pathStack.length > delta ? (pathStack.length - delta - 1) : -1; 770 } 771 772 onPopHandler(popInfo: PopInfo, onPop?: (event: OnPopEvent) => void): void { 773 if (!popInfo?.info || !onPop) { 774 return; 775 } 776 onPop(new OnPopEvent(popInfo.info.name, popInfo.info.param as object, popInfo.result)); 777 } 778 779 getCurrentNavPathInfo(): NavPathInfo { 780 let navPathStack: Array<string> | undefined = this.navPathStack?.getAllPathName(); 781 let navPathInfo: NavPathInfo = (navPathStack && navPathStack.length > 0) ? 782 new NavPathInfo(navPathStack[navPathStack.length - 1], navPathStack.length - 1) : new NavPathInfo(undefined, -1); 783 if (navPathInfo.index >= 0) { 784 navPathInfo.param = this.navPathStack?.getParamByIndex(navPathInfo.index) as object; 785 } 786 return navPathInfo; 787 } 788 789 notifyMessage(): void { 790 if (this.messageDataList.length <= 0) { 791 return; 792 } 793 try { 794 this.onMessage(new OnMessageEvent(this.messageDataList)); 795 } catch (err) { 796 this.consoleError(`onMessage failed, code is ${err.code}, message is ${err.message}`); 797 } 798 this.messageDataList = []; 799 } 800 801 isJsApiEnable(jsApiConfig?: JsApiConfig): boolean { 802 if (!jsApiConfig) { 803 return false; 804 } 805 if (this.compareVersion(jsApiConfig.minVersion, ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION) && 806 this.compareVersion(ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION, jsApiConfig.maxVersion)) { 807 return true; 808 } 809 return false; 810 } 811 812 compareVersion(lowVersion: string, highVersion: string): boolean { 813 if (!lowVersion || !highVersion) { 814 return false; 815 } 816 let v1 = lowVersion.split('.').map(m => Number.parseInt(m)); 817 let v2 = highVersion.split('.').map(m => Number.parseInt(m)); 818 const maxLength = Math.max(v1.length, v2.length); 819 for (let i = 0; i < maxLength; i++) { 820 if (v1[i] < v2[i]) { 821 return true; 822 } else if (v1[i] > v2[i]) { 823 return false; 824 } 825 } 826 if (v1.length < v2.length) { 827 return true; 828 } 829 if (v1.length > v2.length) { 830 return false; 831 } 832 return true; 833 } 834 835 getUri(uriOrFilePath: string): string { 836 if (!uriOrFilePath || uriOrFilePath.startsWith('file://')) { 837 return uriOrFilePath; 838 } 839 return fileUri.getUriFromPath(uriOrFilePath); 840 } 841 842 async checkUploadFile(options: UploadFileOptions): Promise<CheckUploadFileResult> { 843 if (!options.files || options.files.length <= 0) { 844 this.errorWithCodeAndMsg(UPLOAD_IMAGE_FILES_REQUIRED_ERROR, options); 845 return new CheckUploadFileResult(false); 846 } 847 let uriMap: Map<string, string> = new Map(); 848 for (let i = 0; i < options.files?.length; i++) { 849 let file: UploadFile = options.files[i]; 850 if (!file.uri) { 851 this.errorWithCodeAndMsg(UPLOAD_IMAGE_FILES_URI_REQUIRED_ERROR, options); 852 return new CheckUploadFileResult(false); 853 } 854 if (!file.uri.startsWith('file://') && !fs.accessSync(file.uri, fs.AccessModeType.EXIST)) { 855 this.errorWithCodeAndMsg(new AsError(UPLOAD_IMAGE_FILE_NOT_EXIST_ERROR_CODE, 856 `File uri ${file.uri} is not exist.`), options); 857 return new CheckUploadFileResult(false); 858 } 859 let originUri: string = file.uri; 860 let uploadUri: string = file.uri; 861 if (uploadUri.indexOf(UPLOAD_IMAGE_CACHE_DIR) < 0) { 862 let srcUri: string = uploadUri.startsWith('file://') ? uploadUri : fileUri.getUriFromPath(file.uri); 863 uploadUri = this.context.cacheDir + '/' + uploadUri.substring(uploadUri.lastIndexOf('/') + 1); 864 try { 865 await fs.copy(srcUri, fileUri.getUriFromPath(uploadUri)) 866 } catch (err) { 867 this.errorWithCodeAndMsg(UPLOAD_FILE_ERROR, options); 868 return new CheckUploadFileResult(false); 869 } 870 } 871 file.uri = 'internal://' + uploadUri.substring(uploadUri.indexOf(UPLOAD_IMAGE_CACHE_DIR) + 1); 872 uriMap.set(uploadUri, originUri); 873 } 874 return new CheckUploadFileResult(true, uriMap); 875 } 876 877 convertToRequestData(data?: UploadRequestData[]): request.RequestData[] { 878 let requestData: request.RequestData[] = []; 879 if (data) { 880 data.forEach(item => { 881 if (!item.name || !item.value) { 882 return; 883 } 884 requestData.push({ name: item.name, value: item.value }); 885 }); 886 } 887 return requestData; 888 } 889 890 convertToFile(files?: UploadFile[]): request.File[] { 891 let requestFiles: request.File[] = []; 892 if (files) { 893 files.forEach(item => { 894 requestFiles.push({ 895 filename: item.filename, 896 name: item.name, 897 uri: item.uri, 898 type: item.type 899 }); 900 }); 901 } 902 return requestFiles; 903 } 904 905 handleUploadFileResult(taskStateArray: Array<request.TaskState>, uriMap: Map<string, string>, 906 options: UploadFileOptions): void { 907 let taskStates: UploadFileTaskState[] = []; 908 if (taskStateArray) { 909 taskStateArray.forEach(taskState => { 910 let path: (string | undefined) = taskState.path ? uriMap.get(taskState.path) : taskState.path; 911 taskStates.push(new UploadFileTaskState(path ? path : taskState.path, taskState.responseCode, 912 taskState.message)); 913 }); 914 } 915 this.success(new UploadFileResult(taskStates), options); 916 } 917 918 parseFileNameFromUrl(url?: string): string { 919 if (!url) { 920 return ''; 921 } 922 let http: string = url.split('?')[0]; 923 if (http.indexOf('/') < 0) { 924 return ''; 925 } 926 let index: number = http.lastIndexOf('/'); 927 if (index == (http.length - 1)) { 928 return ''; 929 } 930 return http.substring(index + 1); 931 } 932 933 checkAccessToken(permissionName: Permissions): Promise<abilityAccessCtrl.GrantStatus> { 934 let bundleInfo: bundleManager.BundleInfo = bundleManager.getBundleInfoForSelfSync( 935 bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); 936 let tokenId: number = bundleInfo.appInfo.accessTokenId; 937 let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 938 return atManager.checkAccessToken(tokenId, permissionName); 939 } 940 941 checkPermissions(permissionName: Permissions, grantCallback: (err?: BusinessError) => void): void { 942 this.checkAccessToken(permissionName).then(grantStatus => { 943 if (grantStatus == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 944 grantCallback(undefined); 945 } else { 946 let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 947 atManager.requestPermissionsFromUser(this.context, [permissionName]).then(permissionRequestResult => { 948 for (let i = 0; i < permissionRequestResult.authResults.length; i++) { 949 if (permissionRequestResult.authResults[i] != 0) { 950 const error: BusinessError<void> = { 951 name: '', 952 message: `RequestPermissionsFromUser error. authResult: ${permissionRequestResult.authResults[i]}.`, 953 code: PERMISSION_LOCATION_USER_REFUSED_ERROR 954 }; 955 grantCallback(error); 956 return; 957 } 958 } 959 grantCallback(undefined); 960 }).catch((err: BusinessError) => { 961 grantCallback(err); 962 }); 963 } 964 }).catch((err: BusinessError) => { 965 grantCallback(err); 966 }); 967 } 968} 969 970class AtomicServiceApi extends AtomicService { 971 constructor(context: common.UIAbilityContext, navPathStack?: NavPathStack, 972 onMessage?: (event: OnMessageEvent) => void) { 973 super(context, navPathStack, onMessage); 974 } 975 976 pushUrl(options: PushUrlOptions): void { 977 if (!this.checkRouterMode(options.mode, options)) { 978 return; 979 } 980 router.pushUrl({ url: options.url, params: options.params }, this.parseRouterMode(options.mode)).then(() => { 981 this.success(new PushUrlResult(), options); 982 }).catch((err: BusinessError) => { 983 this.error(err, options); 984 }); 985 } 986 987 replaceUrl(options: ReplaceUrlOptions): void { 988 if (!this.checkRouterMode(options.mode, options)) { 989 return; 990 } 991 router.replaceUrl({ url: options.url, params: options.params }, this.parseRouterMode(options.mode)).then(() => { 992 this.success(new ReplaceUrlResult(), options); 993 }).catch((err: BusinessError) => { 994 this.error(err, options); 995 }); 996 } 997 998 backUrl(options: BackUrlOptions): void { 999 if (options.url) { 1000 if (!this.checkBackUrlExists(options.url, options)) { 1001 return; 1002 } 1003 router.back({ url: options.url, params: options.params }); 1004 this.success(new BackUrlResult(), options); 1005 } else if (options.index || options.index === 0) { 1006 if (!this.checkNumberParamPositive('index', options.index, options)) { 1007 return; 1008 } 1009 router.back(options.index, options.params); 1010 this.success(new BackUrlResult(), options); 1011 } else if (options.delta || options.delta === 0) { 1012 if (!this.checkNumberParamPositive('delta', options.delta, options)) { 1013 return; 1014 } 1015 router.back(this.getRouterIndexByDelta(options.delta), options.params); 1016 this.success(new BackUrlResult(), options); 1017 } else { 1018 router.back(); 1019 this.success(new BackUrlResult(), options); 1020 } 1021 } 1022 1023 clearUrl(options: ClearUrlOptions): void { 1024 router.clear(); 1025 this.success(new ClearUrlResult(), options); 1026 } 1027 1028 pushPath(options: PushPathOptions): void { 1029 if (!this.checkNavPathStack('navPathStack.pushPath', options)) { 1030 return; 1031 } 1032 this.navPathStack?.pushPath({ 1033 name: options.name, 1034 param: options.param, 1035 onPop: popInfo => this.onPopHandler(popInfo, options.onPop) 1036 }, options.animated); 1037 this.success(new PushPathResult(), options); 1038 } 1039 1040 replacePath(options: ReplacePathOptions): void { 1041 if (!this.checkNavPathStack('navPathStack.replacePath', options)) { 1042 return; 1043 } 1044 this.navPathStack?.replacePath({ 1045 name: options.name, 1046 param: options.param, 1047 onPop: popInfo => this.onPopHandler(popInfo, options.onPop) 1048 }, options.animated); 1049 this.success(new ReplacePathResult(), options); 1050 } 1051 1052 popPath(options: PopPathOptions): void { 1053 if (!this.checkNavPathStack('navPathStack.pop', options)) { 1054 return; 1055 } 1056 if (options.name) { 1057 let index: number | undefined = this.navPathStack?.popToName(options.name, options.result, options.animated); 1058 if (index === undefined || index === -1) { 1059 this.errorWithCodeAndMsg(POP_PATH_NAME_NOT_EXIST_ERROR, options); 1060 return; 1061 } 1062 } else if (options.index || options.index === 0) { 1063 if (options.index < -1) { 1064 this.errorWithCodeAndMsg(POP_PATH_PARAM_INDEX_INVALID_ERROR, options); 1065 return; 1066 } 1067 if (options.index > this.getCurrentNavPathInfo().index) { 1068 this.errorWithCodeAndMsg(POP_PATH_INDEX_OUT_OF_RANGE_ERROR, options); 1069 return; 1070 } 1071 this.navPathStack?.popToIndex(options.index, options.result, options.animated); 1072 } else if (options.delta || options.delta === 0) { 1073 if (!this.checkNumberParamPositive('delta', options.delta, options)) { 1074 return; 1075 } 1076 this.navPathStack?.popToIndex(this.getNavPathIndexByDelta(options.delta), options.result, options.animated); 1077 } else { 1078 this.navPathStack?.pop(options.result, options.animated); 1079 } 1080 let navPathInfo: NavPathInfo = this.getCurrentNavPathInfo(); 1081 this.success(new PopPathResult(navPathInfo.name, navPathInfo.index, navPathInfo.param), options); 1082 } 1083 1084 clearPath(options: ClearPathOptions): void { 1085 if (!this.checkNavPathStack('navPathStack.clear', options)) { 1086 return; 1087 } 1088 this.navPathStack?.clear(options.animated); 1089 this.success(new ClearPathResult(), options); 1090 } 1091 1092 postMessage(options: PostMessageOptions): void { 1093 options.data && this.messageDataList.push(options.data); 1094 this.success(new PostMessageResult(), options); 1095 } 1096 1097 getEnv(options: GetEnvOptions): void { 1098 let res: GetEnvResult = new GetEnvResult(); 1099 res.deviceType = deviceInfo.deviceType; 1100 res.brand = deviceInfo.brand; 1101 res.productModel = deviceInfo.productModel; 1102 res.osFullName = deviceInfo.osFullName; 1103 this.success(res, options); 1104 } 1105 1106 checkJsApi(options: CheckJsApiOptions): void { 1107 let res: Map<string, boolean> = new Map(); 1108 options.jsApiList?.forEach(jsApi => { 1109 res[jsApi] = this.isJsApiEnable(ATOMIC_SERVICE_JS_API_MAP.get(jsApi)); 1110 }); 1111 this.success(new CheckJsApiResult(res), options); 1112 } 1113 1114 pickCamera(options: PickCameraOptions): void { 1115 picker.pick(this.context, options.mediaTypes as Array<picker.PickerMediaType>, { 1116 cameraPosition: options.cameraPosition, 1117 saveUri: options.saveUri, 1118 videoDuration: options.videoDuration 1119 }).then((pickerResult: picker.PickerResult) => { 1120 this.success(new PickCameraResult(pickerResult.resultCode, pickerResult.resultUri, pickerResult.mediaType), 1121 options); 1122 }).catch((err: BusinessError) => { 1123 this.error(err, options); 1124 }); 1125 } 1126 1127 selectPhoto(options: SelectPhotoOptions): void { 1128 let photoViewPicker = new photoAccessHelper.PhotoViewPicker(); 1129 photoViewPicker.select({ 1130 MIMEType: options.mimeType as photoAccessHelper.PhotoViewMIMETypes, 1131 maxSelectNumber: options.maxSelectNumber, 1132 isPhotoTakingSupported: options.isPhotoTakingSupported, 1133 isEditSupported: options.isEditSupported, 1134 isSearchSupported: options.isSearchSupported, 1135 recommendationOptions: { 1136 recommendationType: options.recommendationType 1137 }, 1138 preselectedUris: options.preselectedUris 1139 }).then((selectResult: photoAccessHelper.PhotoSelectResult) => { 1140 this.success(new SelectPhotoResult(selectResult.photoUris, selectResult.isOriginalPhoto), options); 1141 }).catch((err: BusinessError) => { 1142 this.error(err, options); 1143 }); 1144 } 1145 1146 openPreview(options: OpenPreviewOptions): void { 1147 let uri: string = this.getUri(options.uri as string); 1148 filePreview.openPreview(this.context, { 1149 uri: uri, 1150 mimeType: options.mimeType as string, 1151 title: options.title 1152 }).then(() => { 1153 this.success(new OpenPreviewResult(), options); 1154 }).catch((err: BusinessError) => { 1155 this.error(err, options); 1156 }); 1157 } 1158 1159 uploadFile(options: UploadFileOptions): void { 1160 this.checkUploadFile(options).then(res => { 1161 if (!res.checkResult) { 1162 return; 1163 } 1164 let uploadConfig: request.UploadConfig = { 1165 url: options.url as string, 1166 header: options.header as object, 1167 method: options.method as string, 1168 files: this.convertToFile(options.files), 1169 data: this.convertToRequestData(options.data) 1170 }; 1171 request.uploadFile(this.context, uploadConfig).then((uploadTask: request.UploadTask) => { 1172 uploadTask.on('complete', (taskStateArray: Array<request.TaskState>) => { 1173 this.handleUploadFileResult(taskStateArray, res.uriMap as Map<string, string>, options); 1174 }); 1175 uploadTask.on('fail', (taskStateArray: Array<request.TaskState>) => { 1176 this.handleUploadFileResult(taskStateArray, res.uriMap as Map<string, string>, options); 1177 }); 1178 }).catch((err: BusinessError) => { 1179 this.error(err, options); 1180 }); 1181 }).catch((err: BusinessError) => { 1182 this.error(err, options); 1183 }); 1184 } 1185 1186 downloadFile(options: DownloadFileOptions): void { 1187 let cacheFileName: string = `${util.generateRandomUUID().replaceAll('-', '')}`; 1188 let filePath: string = `${this.context.cacheDir}/${cacheFileName}`; 1189 request.downloadFile(this.context, { 1190 url: options.url, 1191 header: options.header ? options.header : new Object(), 1192 filePath: filePath, 1193 enableMetered: options.enableMetered, 1194 enableRoaming: options.enableRoaming, 1195 networkType: options.networkType, 1196 background: false 1197 }).then((downloadTask: request.DownloadTask) => { 1198 downloadTask.on('complete', () => { 1199 this.success(new DownloadFileResult(filePath), options); 1200 }); 1201 downloadTask.on('fail', errCode => { 1202 this.errorWithCodeAndMsg(new AsError(errCode, 'File download fail.'), options); 1203 }); 1204 }).catch((err: BusinessError) => { 1205 this.error(err, options); 1206 }); 1207 } 1208 1209 getNetworkType(options: GetNetworkTypeOptions): void { 1210 connection.getDefaultNet().then(netHandle => { 1211 if (!netHandle || netHandle.netId === 0) { 1212 this.errorWithCodeAndMsg(NETWORK_NO_ACTIVE_ERROR, options); 1213 return; 1214 } 1215 connection.getNetCapabilities(netHandle).then(netCapabilities => { 1216 let res: GetNetworkTypeResult = new GetNetworkTypeResult(netCapabilities.bearerTypes, 1217 netCapabilities.networkCap, netCapabilities.linkUpBandwidthKbps, netCapabilities.linkDownBandwidthKbps); 1218 this.success(res, options); 1219 }).catch((err: BusinessError) => { 1220 this.error(err, options); 1221 }); 1222 }).catch((err: BusinessError) => { 1223 this.error(err, options); 1224 }); 1225 } 1226 1227 getLocation(options: GetLocationOptions): void { 1228 this.checkPermissions(PERMISSION_APPROXIMATELY_LOCATION, err => { 1229 if (err) { 1230 this.error(err, options); 1231 return; 1232 } 1233 geoLocationManager.getCurrentLocation({ 1234 priority: options.priority, 1235 scenario: options.scenario, 1236 maxAccuracy: options.maxAccuracy, 1237 timeoutMs: options.timeoutMs 1238 }).then(location => { 1239 let res: GetLocationResult = new GetLocationResult(location.latitude, location.longitude, location.altitude, 1240 location.accuracy, location.speed, location.timeStamp, location.direction, location.timeSinceBoot, 1241 location.additions, location.additionSize); 1242 this.success(res, options); 1243 }).catch((err: BusinessError) => { 1244 this.error(err, options); 1245 }); 1246 }); 1247 } 1248} 1249 1250class NavPathInfo { 1251 public name: string | undefined; 1252 public index: number; 1253 public param?: object; 1254 1255 constructor(name: string | undefined, index: number) { 1256 this.name = name; 1257 this.index = index; 1258 } 1259} 1260 1261class CheckUploadFileResult { 1262 public checkResult: boolean; 1263 public uriMap?: Map<string, string>; 1264 1265 constructor(checkResult: boolean, uriMap?: Map<string, string>) { 1266 this.checkResult = checkResult; 1267 this.uriMap = uriMap; 1268 } 1269} 1270 1271class BaseOptions<T> { 1272 public callback?: (err: AsError | undefined, res?: T) => void; 1273} 1274 1275class PushUrlOptions extends BaseOptions<PushUrlResult> { 1276 public url?: string; 1277 public params?: object; 1278 public mode?: string; 1279} 1280 1281class PushUrlResult { 1282} 1283 1284class ReplaceUrlOptions extends BaseOptions<ReplaceUrlResult> { 1285 public url?: string; 1286 public params?: object; 1287 public mode?: string; 1288} 1289 1290class ReplaceUrlResult { 1291} 1292 1293class BackUrlOptions extends BaseOptions<BackUrlResult> { 1294 public url?: string; 1295 public index?: number; 1296 public delta?: number; 1297 public params?: object; 1298} 1299 1300class BackUrlResult { 1301} 1302 1303class ClearUrlOptions extends BaseOptions<ClearUrlResult> { 1304} 1305 1306class ClearUrlResult { 1307} 1308 1309class OnPopEvent { 1310 public name?: string; 1311 public param?: object; 1312 public result?: object; 1313 1314 constructor(name?: string, param?: object, result?: object) { 1315 this.name = name; 1316 this.param = param; 1317 this.result = result; 1318 } 1319} 1320 1321class PushPathOptions extends BaseOptions<PushPathResult> { 1322 public name?: string; 1323 public param?: object; 1324 public animated?: boolean; 1325 public onPop?: (event: OnPopEvent) => void; 1326} 1327 1328class PushPathResult { 1329} 1330 1331class ReplacePathOptions extends BaseOptions<ReplacePathResult> { 1332 public name?: string; 1333 public param?: object; 1334 public animated?: boolean; 1335 public onPop?: (event: OnPopEvent) => void; 1336} 1337 1338class ReplacePathResult { 1339} 1340 1341class PopPathOptions extends BaseOptions<PopPathResult> { 1342 public name?: string; 1343 public index?: number; 1344 public delta?: number; 1345 public result?: object; 1346 public animated?: boolean; 1347} 1348 1349class PopPathResult { 1350 public name: string | undefined; 1351 public index: number; 1352 public param?: object; 1353 1354 constructor(name: string | undefined, index: number, param?: object) { 1355 this.name = name; 1356 this.index = index; 1357 this.param = param; 1358 } 1359} 1360 1361class ClearPathOptions extends BaseOptions<ClearPathResult> { 1362 public animated?: boolean; 1363} 1364 1365class ClearPathResult { 1366} 1367 1368class PostMessageOptions extends BaseOptions<PostMessageResult> { 1369 public data?: object; 1370} 1371 1372class PostMessageResult { 1373} 1374 1375export class OnMessageEvent { 1376 public data: object[]; 1377 1378 constructor(data: object[]) { 1379 this.data = data; 1380 } 1381} 1382 1383export class OnErrorReceiveEvent { 1384 public request: WebResourceRequest; 1385 public error: WebResourceError; 1386 1387 constructor(request: WebResourceRequest, error: WebResourceError) { 1388 this.request = request; 1389 this.error = error; 1390 } 1391} 1392 1393export class OnHttpErrorReceiveEvent { 1394 public request: WebResourceRequest; 1395 public response: WebResourceResponse; 1396 1397 constructor(request: WebResourceRequest, response: WebResourceResponse) { 1398 this.request = request; 1399 this.response = response; 1400 } 1401} 1402 1403export class OnPageBeginEvent { 1404 public url: string; 1405 1406 constructor(url: string) { 1407 this.url = url; 1408 } 1409} 1410 1411export class OnPageEndEvent { 1412 public url: string; 1413 1414 constructor(url: string) { 1415 this.url = url; 1416 } 1417} 1418 1419export class WebHeader { 1420 public headerKey: string; 1421 public headerValue: string; 1422 1423 constructor(headerKey: string, headerValue: string) { 1424 this.headerKey = headerKey; 1425 this.headerValue = headerValue; 1426 } 1427} 1428 1429class GetEnvOptions extends BaseOptions<GetEnvResult> { 1430} 1431 1432class GetEnvResult { 1433 public deviceType?: string; 1434 public brand?: string; 1435 public productModel?: string; 1436 public osFullName?: string; 1437} 1438 1439class CheckJsApiOptions extends BaseOptions<CheckJsApiResult> { 1440 public jsApiList?: string[]; 1441} 1442 1443class CheckJsApiResult { 1444 public checkResult?: Map<string, boolean>; 1445 1446 constructor(checkResult?: Map<string, boolean>) { 1447 this.checkResult = checkResult; 1448 } 1449} 1450 1451class PickCameraOptions extends BaseOptions<PickCameraResult> { 1452 public mediaTypes?: string[]; 1453 public cameraPosition?: number; 1454 public saveUri?: string; 1455 public videoDuration?: number; 1456} 1457 1458class PickCameraResult { 1459 public resultCode?: number; 1460 public resultUri?: string; 1461 public mediaType?: string; 1462 1463 constructor(resultCode?: number, resultUri?: string, mediaType?: string) { 1464 this.resultCode = resultCode; 1465 this.resultUri = resultUri; 1466 this.mediaType = mediaType; 1467 } 1468} 1469 1470class SelectPhotoOptions extends BaseOptions<SelectPhotoResult> { 1471 public mimeType?: string; 1472 public maxSelectNumber?: number; 1473 public isPhotoTakingSupported?: boolean; 1474 public isEditSupported?: boolean; 1475 public isSearchSupported?: boolean; 1476 public recommendationType?: number; 1477 public preselectedUris?: string[]; 1478} 1479 1480class SelectPhotoResult { 1481 public photoUris?: string[]; 1482 public isOriginalPhoto?: boolean; 1483 1484 constructor(photoUris?: string[], isOriginalPhoto?: boolean) { 1485 this.photoUris = photoUris; 1486 this.isOriginalPhoto = isOriginalPhoto; 1487 } 1488} 1489 1490class OpenPreviewOptions extends BaseOptions<OpenPreviewResult> { 1491 public title?: string; 1492 public uri?: string; 1493 public mimeType?: string; 1494} 1495 1496class OpenPreviewResult { 1497} 1498 1499class UploadFileOptions extends BaseOptions<UploadFileResult> { 1500 public url?: string; 1501 public header?: object; 1502 public method?: string; 1503 public files?: UploadFile[]; 1504 public data?: UploadRequestData[]; 1505} 1506 1507class UploadFile { 1508 public filename: string; 1509 public name: string; 1510 public uri: string; 1511 public type: string; 1512 1513 constructor(filename: string, name: string, uri: string, type: string) { 1514 this.filename = filename; 1515 this.name = name; 1516 this.uri = uri; 1517 this.type = type; 1518 } 1519} 1520 1521class UploadRequestData { 1522 public name?: string; 1523 public value?: string; 1524} 1525 1526class UploadFileResult { 1527 public taskStates?: UploadFileTaskState[]; 1528 1529 constructor(taskStates?: UploadFileTaskState[]) { 1530 this.taskStates = taskStates; 1531 } 1532} 1533 1534class UploadFileTaskState { 1535 public path?: string; 1536 public responseCode?: number; 1537 public message?: string; 1538 1539 constructor(path?: string, responseCode?: number, message?: string) { 1540 this.path = path; 1541 this.responseCode = responseCode; 1542 this.message = message; 1543 } 1544} 1545 1546class DownloadFileOptions extends BaseOptions<DownloadFileResult> { 1547 public url?: string; 1548 public header?: object; 1549 public fileName?: string; 1550 public enableMetered?: boolean; 1551 public enableRoaming?: boolean; 1552 public networkType?: number; 1553} 1554 1555class DownloadFileResult { 1556 public uri?: string; 1557 1558 constructor(uri?: string) { 1559 this.uri = uri; 1560 } 1561} 1562 1563class GetNetworkTypeOptions extends BaseOptions<GetNetworkTypeResult> { 1564} 1565 1566class GetNetworkTypeResult { 1567 public bearerTypes: number[]; 1568 public networkCap?: number[]; 1569 public linkUpBandwidthKbps?: number; 1570 public linkDownBandwidthKbps?: number; 1571 1572 constructor(bearerTypes: number[], networkCap?: number[], linkUpBandwidthKbps?: number, 1573 linkDownBandwidthKbps?: number) { 1574 this.bearerTypes = bearerTypes; 1575 this.networkCap = networkCap; 1576 this.linkUpBandwidthKbps = linkUpBandwidthKbps; 1577 this.linkDownBandwidthKbps = linkDownBandwidthKbps; 1578 } 1579} 1580 1581class GetLocationOptions extends BaseOptions<GetLocationResult> { 1582 public priority?: number; 1583 public scenario?: number; 1584 public maxAccuracy?: number; 1585 public timeoutMs?: number; 1586} 1587 1588class GetLocationResult { 1589 public latitude: number; 1590 public longitude: number; 1591 public altitude: number; 1592 public accuracy: number; 1593 public speed: number; 1594 public timeStamp: number; 1595 public direction: number; 1596 public timeSinceBoot: number; 1597 public additions?: string[] | undefined; 1598 public additionSize?: number; 1599 1600 constructor(latitude: number, longitude: number, altitude: number, accuracy: number, speed: number, 1601 timeStamp: number, direction: number, timeSinceBoot: number, additions?: string[], additionSize?: number) { 1602 this.latitude = latitude; 1603 this.longitude = longitude; 1604 this.altitude = altitude; 1605 this.accuracy = accuracy; 1606 this.speed = speed; 1607 this.timeStamp = timeStamp; 1608 this.direction = direction; 1609 this.timeSinceBoot = timeSinceBoot; 1610 this.additions = additions; 1611 this.additionSize = additionSize; 1612 } 1613} 1614