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