• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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