diff --git a/atomicserviceweb/interfaces/atomicserviceweb.js b/atomicserviceweb/interfaces/atomicserviceweb.js index a9b53c9caa5527053c1880d3714ede6ec48d42ce..f9e3584d2a2b93d193ca3dd3dcd0f92b5168efed 100644 --- a/atomicserviceweb/interfaces/atomicserviceweb.js +++ b/atomicserviceweb/interfaces/atomicserviceweb.js @@ -32,7 +32,7 @@ if (!("finalizeConstruction" in ViewPU.prototype)) { Reflect.set(ViewPU.prototype, "finalizeConstruction", () => { }); } -const web_webview = requireNapi('web.webview'); +const webView = requireNapi('web.webview'); const router = requireNapi('router'); const deviceInfo = requireNapi('deviceInfo'); const geoLocationManager = requireNapi('geoLocationManager'); @@ -47,8 +47,26 @@ const filePreview = globalThis.requireNapi('filemanagement.filepreview', false, const fileUri = requireNapi('file.fileuri'); const picker = requireNapi('multimedia.cameraPicker'); const filePicker = requireNapi('file.picker'); +const call = requireNapi('telephony.call'); const atomicServiceWebNapi = requireInternal('atomicservice.AtomicServiceWeb'); +let atomicBasicEngine = null; + +function loadAtomicBasicEngine() { + try { + import('@hms:atomicservicedistribution.atomicbasicengine').then((z10) => { + console.log('AtomicServiceWeb loadAtomicBasicEngine success'); + atomicBasicEngine = z10; + }).catch((y10) => { + console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + y10.message); + }); + } catch (x10) { + console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + x10.message); + } +} + +loadAtomicBasicEngine(); + class AsError { constructor(m11, n11) { this.code = m11; @@ -77,6 +95,10 @@ const registerJsApi = (u11, v11, w11, x11, y11) => { const MAX_VERSION = '99.99.99'; const ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION = '1.0.0'; const PERMISSION_APPROXIMATELY_LOCATION = 'ohos.permission.APPROXIMATELY_LOCATION'; +const TEL_PROTOCOL = 'tel:'; +const MAILTO_PROTOCOL = 'mailto:'; +const WANT_ACTION_SEND_TO_DATA = 'ohos.want.action.sendToData'; +const RESOURCE_RAWFILE = 'resource://rawfile'; const SYSTEM_INTERNAL_ERROR = new AsError(500, 'System internal error.'); const JS_API_INVALID_INVOKE_ERROR = new AsError(200001, 'Invalid invoke.'); const PARAM_REQUIRED_ERROR_CODE = 200002; @@ -142,8 +164,8 @@ export class AtomicServiceWeb extends ViewPU { this.onControllerAttached = undefined; this.onLoadIntercept = undefined; this.context = this.getUIContext().getHostContext(); - this.webViewController = new web_webview.WebviewController(); - this.schemeHandler = new web_webview.WebSchemeHandler(); + this.webViewController = new webView.WebviewController(); + this.schemeHandler = new webView.WebSchemeHandler(); this.atomicService = undefined; this.atomicServiceProxy = undefined; this.setInitiallyProvidedValue(t10); @@ -270,11 +292,12 @@ export class AtomicServiceWeb extends ViewPU { let h2 = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); if (h2?.appInfo?.appProvisionType === 'debug') { console.log(`AtomicServiceWeb setWebDebuggingAccess`); - web_webview.WebviewController.setWebDebuggingAccess(true); + webView.WebviewController.setWebDebuggingAccess(true); } } catch (d2) { console.error(`AtomicServiceWeb set Web Debug Mode failed, code is ${d2.code}, message is ${d2.message}`); } + this.initDomainCheckLog(); } aboutToDisappear() { @@ -300,8 +323,9 @@ export class AtomicServiceWeb extends ViewPU { Web.onControllerAttached(() => { this.registerJavaScriptProxy(); this.schemeHandler.onRequestStart((z10) => { - return !this.checkUrl(z10.getRequestUrl()); + return !this.interceptUrl(z10.getRequestUrl(), z10.isMainFrame(), z10.getRequestResourceType()); }); + this.webViewController.setWebSchemeHandler('http', this.schemeHandler); this.webViewController.setWebSchemeHandler('https', this.schemeHandler); this.initAtomicServiceWebController(); if (this.onControllerAttached) { @@ -313,7 +337,7 @@ export class AtomicServiceWeb extends ViewPU { } }); Web.onOverrideUrlLoading((i10) => { - return !this.checkUrl(i10.getRequestUrl()); + return !this.interceptOverrideUrlLoading(i10.getRequestUrl()); }); Web.onLoadIntercept(m7 => { let n7 = !this.checkUrl(m7.data.getRequestUrl()); @@ -377,6 +401,7 @@ export class AtomicServiceWeb extends ViewPU { let t1 = 'webView'; q1 = this.cutUrl(q1); let res = atomicServiceWebNapi.checkUrl(w1, t1, q1); + console.debug(`AtomicServiceWeb checkUrl ret=${res === 0} url=${q1}`); return res === 0; } catch (j2) { let n2 = j2; @@ -385,6 +410,124 @@ export class AtomicServiceWeb extends ViewPU { } } + initDomainCheckLog() { + try { + let a9 = this.getAppId(); + let b9 = this.isCheckDomainBlockListAvailable(); + console.debug('AtomicServiceWeb initDomainCheckLog appId=' + a9 + ' checkDomainBlockListAvailable=' + b9); + } catch (z8) { + console.error('AtomicServiceWeb initDomainCheckLog error, message: ' + z8.message); + } + } + + getAppId() { + let x8 = this.context.abilityInfo.bundleName; + if (!x8) { + return null; + } + let y8 = x8.split('.'); + if (!y8 || y8.length <= 0) { + return null; + } + return y8[y8.length - 1]; + } + + isMainPageOrIframeRequest(m8, w8) { + if (m8) { + return true; + } + if (w8 === webView.WebResourceType.MAIN_FRAME || + w8 === webView.WebResourceType.SUB_FRAME) { + return true; + } + return false; + } + + isCheckDomainBlockListAvailable() { + if (!atomicBasicEngine || !atomicBasicEngine.default) { + return false; + } + return typeof atomicBasicEngine.default.checkDomainBlockList === 'function'; + } + + interceptUrl(s8, t8, u8) { + if (!s8) { + return false; + } + if (s8.startsWith(RESOURCE_RAWFILE)) { + return true; + } + if (this.isMainPageOrIframeRequest(t8, u8)) { + return this.checkUrl(s8); + } + if (this.isCheckDomainBlockListAvailable()) { + return this.checkUrlNew(s8); + } + return true; + } + + checkUrlNew(n8) { + let o8 = this.getAppId(); + if (!o8) { + console.error('AtomicServiceWeb checkUrlNew error, appId is invalid'); + return false; + } + try { + let q8 = this.cutUrl(n8); + let r8 = atomicBasicEngine.default.checkDomainBlockList(q8, o8); + console.debug(`AtomicServiceWeb checkUrlNew ret=${!r8} url=${q8}`); + return !r8; + } catch (p8) { + console.error(`AtomicServiceWeb checkUrlNew error, code: ${p8.code}, message: ${p8.message}`); + return true; + } + } + + interceptOverrideUrlLoading(m8) { + if (!m8) { + return false; + } + if (m8.startsWith(TEL_PROTOCOL)) { + this.openMakeCall(m8); + return false; + } + if (m8.startsWith(MAILTO_PROTOCOL)) { + this.openSendMail(m8); + return false; + } + return this.checkUrl(m8); + } + + openMakeCall(i8) { + if (!i8 || !i8.startsWith(TEL_PROTOCOL)) { + return; + } + try { + let k8 = i8.substring(TEL_PROTOCOL.length); + call.makeCall(k8).catch((l8) => { + console.error(`AtomicServiceWeb openMakeCall error, code: ${l8.code}, message: ${l8.message}`); + }); + } catch (j8) { + console.error(`AtomicServiceWeb openMakeCall error, code: ${j8.code}, message: ${j8.message}`); + } + } + + openSendMail(f8) { + if (!f8 || !f8.startsWith(MAILTO_PROTOCOL)) { + return; + } + try { + this.context.startAbility({ + action: WANT_ACTION_SEND_TO_DATA, + uri: f8 + }).catch((h8) => { + console.error(`AtomicServiceWeb openSendMail error, code: ${h8.code}, message: ${h8.message}`); + }); + } catch (g8) { + console.error(`AtomicServiceWeb openSendMail error, code: ${g8.code}, message: ${g8.message}`); + } + } + rerender() { this.updateDirtyElements(); } diff --git a/atomicserviceweb/source/atomicserviceweb.ets b/atomicserviceweb/source/atomicserviceweb.ets index e6c9c9ccf6f1dc838be52a5d6efb5a64621d9b73..1a5534c460624dbe8e6b8c72a3f295c35cdb340e 100644 --- a/atomicserviceweb/source/atomicserviceweb.ets +++ b/atomicserviceweb/source/atomicserviceweb.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import web_webview from '@ohos.web.webview'; +import webView from '@ohos.web.webview'; import router from '@ohos.router'; import deviceInfo from '@ohos.deviceInfo'; import common from '@ohos.app.ability.common'; @@ -30,6 +30,27 @@ import fileUri from '@ohos.file.fileuri'; import picker from '@ohos.multimedia.cameraPicker'; import filePicker from '@ohos.file.picker'; import { BusinessError } from '@ohos.base'; +import { call } from '@kit.TelephonyKit'; + +let atomicBasicEngine: ESObject | null = null; + +/** + * 初始化加载atomicbasicengine + */ +function loadAtomicBasicEngine(): void { + try { + import('@hms.atomicservicedistribution.atomicbasicengine').then((ns: ESObject) => { + console.log('AtomicServiceWeb loadAtomicBasicEngine success'); + atomicBasicEngine = ns; + }).catch((err: BusinessError) => { + console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + err.message); + }); + } catch (err) { + console.error('AtomicServiceWeb loadAtomicBasicEngine error, message: ' + err.message); + } +} + +loadAtomicBasicEngine(); class AsError { public code: number; @@ -68,6 +89,10 @@ const registerJsApi = (apiNameAlias: string, apiName: string, minVersion: string const MAX_VERSION = '99.99.99'; const ATOMIC_SERVICE_JS_SDK_CURRENT_VERSION = '1.0.0'; const PERMISSION_APPROXIMATELY_LOCATION: Permissions = 'ohos.permission.APPROXIMATELY_LOCATION'; +const TEL_PROTOCOL: string = 'tel:'; +const MAILTO_PROTOCOL: string = 'mailto:'; +const WANT_ACTION_SEND_TO_DATA: string = 'ohos.want.action.sendToData'; +const RESOURCE_RAWFILE: string = 'resource://rawfile'; const SYSTEM_INTERNAL_ERROR: AsError = new AsError(500, 'System internal error.'); const JS_API_INVALID_INVOKE_ERROR: AsError = new AsError(200001, 'Invalid invoke.'); @@ -130,8 +155,8 @@ export struct AtomicServiceWeb { public onControllerAttached?: VoidCallback; public onLoadIntercept?: Callback; private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext; - private webViewController: web_webview.WebviewController = new web_webview.WebviewController(); - private schemeHandler: web_webview.WebSchemeHandler = new web_webview.WebSchemeHandler(); + private webViewController: webView.WebviewController = new webView.WebviewController(); + private schemeHandler: webView.WebSchemeHandler = new webView.WebSchemeHandler(); private atomicService?: AtomicService; private atomicServiceProxy?: AtomicServiceProxy; @@ -146,11 +171,13 @@ export struct AtomicServiceWeb { bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); if (bundleInfo?.appInfo?.appProvisionType === 'debug') { console.log(`AtomicServiceWeb setWebDebuggingAccess`); - web_webview.WebviewController.setWebDebuggingAccess(true); + webView.WebviewController.setWebDebuggingAccess(true); } } catch (err) { console.error(`AtomicServiceWeb set Web Debug Mode failed, code is ${err.code}, message is ${err.message}`); } + + this.initDomainCheckLog(); } aboutToDisappear(): void { @@ -177,9 +204,10 @@ export struct AtomicServiceWeb { this.onProgressChange)) .onControllerAttached(() => { this.registerJavaScriptProxy(); - this.schemeHandler.onRequestStart((request: web_webview.WebSchemeHandlerRequest) => { - return !this.checkUrl(request.getRequestUrl()); + this.schemeHandler.onRequestStart((request: webView.WebSchemeHandlerRequest) => { + return !this.interceptUrl(request.getRequestUrl(), request.isMainFrame(), request.getRequestResourceType()); }); + this.webViewController.setWebSchemeHandler('http', this.schemeHandler); this.webViewController.setWebSchemeHandler('https', this.schemeHandler); this.initAtomicServiceWebController(); if (this.onControllerAttached) { @@ -191,7 +219,7 @@ export struct AtomicServiceWeb { } }) .onOverrideUrlLoading((webResourceRequest: WebResourceRequest) => { - return !this.checkUrl(webResourceRequest.getRequestUrl()); + return !this.interceptOverrideUrlLoading(webResourceRequest.getRequestUrl()); }) .onLoadIntercept(event => { let checkResult = !this.checkUrl(event.data.getRequestUrl()); @@ -203,7 +231,7 @@ export struct AtomicServiceWeb { return true; } } - return checkResult + return checkResult; }) } @@ -252,13 +280,165 @@ export struct AtomicServiceWeb { url = this.cutUrl(url); return true; } + + /** + * 初始化域名校验日志 + */ + initDomainCheckLog(): void { + try { + let appId: string | null = this.getAppId(); + let checkDomainBlockListAvailable: boolean = this.isCheckDomainBlockListAvailable(); + console.debug('AtomicServiceWeb initDomainCheckLog appId=' + appId + ' checkDomainBlockListAvailable=' + + checkDomainBlockListAvailable); + } catch (err) { + console.error('AtomicServiceWeb initDomainCheckLog error, message: ' + err.message); + } + } + + /** + * 获取appid + */ + getAppId(): string | null { + let bundleName: string = this.context.abilityInfo.bundleName; + if (!bundleName) { + return null; + } + let strArray: string[] = bundleName.split('.'); + if (!strArray || strArray.length <= 0) { + return null; + } + return strArray[strArray.length - 1]; + } + + /** + * 是否主页面或iframe页面请求 + */ + isMainPageOrIframeRequest(isMainFrame: boolean, requestResourceType: number): boolean { + if (isMainFrame) { + return true; + } + if (requestResourceType === webView.WebResourceType.MAIN_FRAME || + requestResourceType === webView.WebResourceType.SUB_FRAME) { + return true; + } + return false; + } + + /** + * 检查checkDomainBlockList接口是否可用 + */ + isCheckDomainBlockListAvailable(): boolean { + if (!atomicBasicEngine || !atomicBasicEngine.default) { + return false; + } + return typeof atomicBasicEngine.default.checkDomainBlockList === 'function'; + } + + /** + * 拦截url请求 + */ + interceptUrl(url: string, isMainFrame: boolean, requestResourceType: number): boolean { + if (!url) { + return false; + } + if (url.startsWith(RESOURCE_RAWFILE)) { + return true; + } + // 主页面或iframe页面,走域名白名单校验 + if (this.isMainPageOrIframeRequest(isMainFrame, requestResourceType)) { + return this.checkUrl(url); + } + // 非主页面或iframe页面,atomicBasicEngine可用,走域名黑名单校验 + if (this.isCheckDomainBlockListAvailable()) { + return this.checkUrlNew(url); + } + // 非主页面或iframe页面,atomicBasicEngine不可用,放通管控 + return true; + } + + /** + * 域名黑名单校验 + */ + checkUrlNew(url: string): boolean { + let appId: string | null = this.getAppId(); + if (!appId) { + console.error('AtomicServiceWeb checkUrlNew error, appId is invalid'); + return false; + } + try { + let shortUrl: string = this.cutUrl(url); + let isInDomainBlockList: boolean = atomicBasicEngine.default.checkDomainBlockList(shortUrl, appId); + console.debug(`AtomicServiceWeb checkUrlNew ret=${!isInDomainBlockList} url=${shortUrl}`); + return !isInDomainBlockList; + } catch (err) { + console.error(`AtomicServiceWeb checkUrlNew error, code: ${err.code}, message: ${err.message}`); + // 黑名单接口调用失败,放通管控 + return true; + } + } + + /** + * 拦截url跳转 + */ + interceptOverrideUrlLoading(url: string): boolean { + if (!url) { + return false; + } + // 处理tel:协议 + if (url.startsWith(TEL_PROTOCOL)) { + this.openMakeCall(url); + return false; + } + // 处理mailto:协议 + if (url.startsWith(MAILTO_PROTOCOL)) { + this.openSendMail(url); + return false; + } + return this.checkUrl(url); + } + + /** + * 拉起打电话 + */ + openMakeCall(url: string): void { + if (!url || !url.startsWith(TEL_PROTOCOL)) { + return; + } + try { + let phoneNumber: string = url.substring(TEL_PROTOCOL.length); + call.makeCall(phoneNumber).catch((err: BusinessError) => { + console.error(`AtomicServiceWeb openMakeCall error, code: ${err.code}, message: ${err.message}`); + }); + } catch (err) { + console.error(`AtomicServiceWeb openMakeCall error, code: ${err.code}, message: ${err.message}`); + } + } + + /** + * 拉起发邮件 + */ + openSendMail(url: string): void { + if (!url || !url.startsWith(MAILTO_PROTOCOL)) { + return; + } + try { + this.context.startAbility({ + action: WANT_ACTION_SEND_TO_DATA, + uri: url + }).catch((err: BusinessError) => { + console.error(`AtomicServiceWeb openSendMail error, code: ${err.code}, message: ${err.message}`); + }); + } catch (err) { + console.error(`AtomicServiceWeb openSendMail error, code: ${err.code}, message: ${err.message}`); + } + } } @Observed export class AtomicServiceWebController { - private webViewController?: web_webview.WebviewController; + private webViewController?: webView.WebviewController; - setWebviewController(webViewController: web_webview.WebviewController): void { + setWebviewController(webViewController: webView.WebviewController): void { this.webViewController = webViewController; }