DEV Community

Cover image for The cross-domain issue of local resources for Web components
liu yang
liu yang

Posted on

The cross-domain issue of local resources for Web components

Handling Cross-Origin Requests for Local Resources in HarmonyOS Next

Intercepting Cross-Origin Requests

To enhance security, the ArkWeb kernel does not allow the file or resource protocols to access requests from cross-origin contexts. Therefore, when using Web components to load local offline resources, the Web component will intercept cross-origin access for the file and resource protocols. Developers can resolve this by setting a path list and using the file protocol to access resources within that list, allowing cross-origin access to local files.

When the Web component cannot access local cross-origin resources, developers can see error messages similar to the following in the DevTools console:

Access to script at 'xxx' from origin 'xxx' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, arkweb, data, chrome-extension, chrome, https, chrome-untrusted.
Enter fullscreen mode Exit fullscreen mode

Solutions for Cross-Origin Requests to Local Resources

Method 1: Using HTTP or HTTPS Protocols

To enable the Web component to successfully access cross-origin resources, developers should use http or https protocols instead of the file or resource protocols. The URL domain should be a custom domain constructed for personal or organizational use to avoid conflicts with actual domains on the internet. Additionally, developers need to use the onInterceptRequest method of the Web component to intercept and replace local resources.

The following example demonstrates how to resolve the issue of failed cross-origin access to local resources. In this example, index.html and js/script.js are placed in the rawfile directory of the project. If the resource protocol is used to access index.html, js/script.js will be intercepted due to cross-origin restrictions and will fail to load. In the example, the https://www.example.com/ domain replaces the original resource protocol, and the onInterceptRequest interface is used to replace the resources, allowing js/script.js to load successfully and resolving the cross-origin interception issue.

// main/ets/pages/Index.ets
import { webview } from '@kit.ArkWeb';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  webviewController: webview.WebviewController = new webview.WebviewController();
  // Construct a mapping table of domain names and local files
  schemeMap = new Map([
    ["https://www.example.com/index.html", "index.html"],
    ["https://www.example.com/js/script.js", "js/script.js"],
  ])
  // Construct a mapping table of local files and their corresponding mimeType
  mimeTypeMap = new Map([
    ["index.html", 'text/html'],
    ["js/script.js", "text/javascript"]
  ])

  build() {
    Row() {
      Column() {
        // For local index.html, use http or https protocol instead of file or resource protocol, and construct a custom domain.
        // In this example, www.example.com is used.
        Web({ src: "https://www.example.com/index.html", controller: this.webviewController })
          .javaScriptAccess(true)
          .fileAccess(true)
          .domStorageAccess(true)
          .geolocationAccess(true)
          .width("100%")
          .height("100%")
          .onInterceptRequest((event) => {
            if (!event) {
              return;
            }
            // Match and replace local offline resources to bypass cross-origin restrictions
            if (this.schemeMap.has(event.request.getRequestUrl())) {
              let rawfileName: string = this.schemeMap.get(event.request.getRequestUrl())!;
              let mimeType = this.mimeTypeMap.get(rawfileName);
              if (typeof mimeType === 'string') {
                let response = new WebResourceResponse();
                // Construct response data; if the local file is in rawfile, set it as follows
                response.setResponseData($rawfile(rawfileName));
                response.setResponseEncoding('utf-8');
                response.setResponseMimeType(mimeType);
                response.setResponseCode(200);
                response.setReasonMessage('OK');
                response.setResponseIsReady(true);
                return response;
              }
            }
            return null;
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode
<!-- main/resources/rawfile/index.html -->
<html>
<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<script crossorigin src="./js/script.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
// main/resources/rawfile/js/script.js
const body = document.body;
const element = document.createElement('div');
element.textContent = 'success';
body.appendChild(element);
Enter fullscreen mode Exit fullscreen mode

Method 2: Setting a Path List with setPathAllowingUniversalAccess

Use the setPathAllowingUniversalAccess method to set a path list. When using the file protocol to access resources in this list, cross-origin access to local files is allowed. Additionally, once the path list is set, the file protocol will be limited to accessing resources within the list (overriding the behavior of fileAccess). Paths in the list must conform to one of the following formats:

  1. Application file directory obtained via Context.filesDir, with subdirectory examples:

    • /data/storage/el2/base/files/example
    • /data/storage/el2/base/haps/entry/files/example
  2. Application resource directory obtained via Context.resourceDir, with subdirectory examples:

    • /data/storage/el1/bundle/entry/resource/resfile
    • /data/storage/el1/bundle/entry/resource/resfile/example

If any path in the list does not meet the above conditions, the system will throw an exception with code 401, indicating that the path list setting has failed. If the path list is empty, the accessibility of the file protocol will follow the rules of fileAccess.

// main/ets/pages/Index.ets
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct WebComponent {
  controller: WebviewController = new webview.WebviewController();

  build() {
    Row() {
      Web({ src: "", controller: this.controller })
        .onControllerAttached(() => {
          try {
            // Set the path list allowing cross-origin access
            this.controller.setPathAllowingUniversalAccess([
              getContext().resourceDir,
              getContext().filesDir + "/example"
            ])
            this.controller.loadUrl("file://" + getContext().resourceDir + "/index.html")
          } catch (error) {
            console.error(`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`);
          }
        })
        .javaScriptAccess(true)
        .fileAccess(true)
        .domStorageAccess(true)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  • Method 1: This method involves using the https protocol to load local resources and intercepting requests using the onInterceptRequest method to replace local resources with custom URLs.

Top comments (0)