# # Integration of ServiceWorker

## # Purpose

This section aims to provide developers a comprehensive guide for integrating and consolidating the built-in ServiceWorker functionality in the SDK at the application layer. It addresses the ServiceWorker conflict issues and common problems.

Although the browser allows multiple Service Workers to be registered in the same domain, these Service Workers must have different scopes. In this way, each Service Worker can independently control the resources under different scopes. However, `MessageWorker.js` occupies a scope (default is '/'), which will cause the application layer's Service Worker to fail to register successfully in the same scope. Therefore, when the Service Worker at the application layer needs to share the same scope with the SDK's built-in `MessageWorker.js`, they need to be merged into a single file.

This section will introduce how to merge and customize parameters related to Service Worker configuration.

## # Introduction to ServiceWorker

Service Worker is a script that runs in the background of a browser, which can be used to implement offline capabilities, push notifications, background synchronization, and other features. After the webpage is fully loaded, it operates in the background and can intercept network requests, cache resources, and provide other functions.

Key features of ServiceWorker：

- Offline Capabilities: ServiceWorker can cache resources, allowing users to access websites even when the network is disconnected.
- Push Notifications: ServiceWorker can receive push notifications from the server and display messages to the user.
- Background Synchronization: ServiceWorker can perform tasks in the background, such as uploading data or updating data.
- Network Interception: ServiceWorker can intercept network requests and process them as needed, such as caching data or modifying request headers.

## # Overview of MessageWorker.js

`MessageWorker.js` is a Service Worker script provided by the SDK to implement synchronous communication in the Web Worker layer. Since `MessageWorker.js` occupies an independent scope, when the application layer's Service Worker needs to use the same scope, the two need to be merged into a single file.

## # Environment Setup

Before starting to integrate Service Worker, please ensure your development environment meets the following requirements:

- Browser supporting Service Worker: Ensure your target browser supports Service Worker, such as Chrome, Firefox, and Edge. For more specific information, you can refer to Can I use
  (opens new window).

- `@foxitsoftware/foxit-pdf-sdk-for-web-library`: The version must be `10.0.0` or higher, and the `lib` directory should contain the `MessageWorker.js` file.

- HTTPS support: Your website must be accessed via HTTPS to use Service Worker. However, there are exceptions for the development environment. `localhost` and `127.0.0.1` can use Service Worker without enabling HTTPS access.

## # Registration and Configuration of Service Worker

Starting from `10.0.0`, a new `messageSyncServiceWorker`
(opens new window) parameter has been added to the `PDFViewer` constructor arguments, used for specifying the registration method of the Service Worker.

There are two ways to use `messageSyncServiceWorker`:

1. Method 1: Specify `url` and `options`:

   - `url` is the registration path for the Service Worker;
   - `options` are the registration options for the Service Worker, you can refer to the specific description in MDN
     (opens new window).

       const viewer = new PDFViewer({
           messageSyncServiceWorker: {
               url: '/your-service-worker.js',
               options: { // Optional
                   scope: '/foxit-lib/'
               }
           }
           // ... Other parameters
       })

2. Method 2: Specify `registration`:

   - `registration` is the `Promise<ServiceWorkerRegistration>` object returned by the `navigator.serviceWorker.register()` method.

       const viewer = new PDFViewer({
           messageSyncServiceWorker: {
               registration: navigator.serviceWorker.register('/your-service-worker.js', {
                   scope: '/foxit-lib/'
               })
           }
           // ... Other parameters
       })

Method 1 will be determined by the SDK internally as to when to register the ServiceWorker. If you need to manually control the registration of the ServiceWorker, you must use method 2.

In the SDK release package, we have provided a complete example (`/examples/PDFViewCtrl/integrate-service-worker`). You can directly refer to its implementation and run and view the effects according to the README.md document.

## # `Service-Worker-Allowed` Response Header

By default, the max scope allowed by a Service Worker is determined by its script location. Specifically, the scope of the Service Worker can only cover the directory where its script is located and its subdirectories. For example, if the Service Worker script is located at `https://example.com/sub/worker.js`, it can only control resources under `https://example.com/sub/` and its subpaths by default. If you forcibly specify the scope parameter to a larger scope, it will lead to the failure of the Service Worker registration, and the error message will be: `The path of the provided scope ('/') is not under the max scope allowed ('/sub/')`.

However, in some cases, you may want to expand the scope of the Service Worker so that it can control resources within a larger range. At this point, the `Service-Worker-Allowed` response header becomes particularly important. By configuring this response header, you can specify a broader path, allowing the Service Worker to be effective within a larger scope.

### # Configuring `Service-Worker-Allowed` Response Header

To use the `Service-Worker-Allowed` response header, you need to include the following field in the HTTP response header of the Service Worker script. Its value is the max scope path allowed:

    Service-Worker-Allowed /;

#### # Nginx Configuration Example

If you are using Nginx as your server, you can include the `Service-Worker-Allowed` response header by modifying the Nginx configuration file. Below is a configuration example:

    server {
        location /sw.js {
            add_header Service-Worker-Allowed /;
        }
    }

#### # Webpack Dev Server Configuration Example

If you are using Webpack Dev Server for local development, you can include the `Service-Worker-Allowed` response header by configuring devServer. Below is a configuration example:

    // webpack.config.js
    module.exports = {
        //  Other configurations
        devServer: {
            headers: {
                'Service-Worker-Allowed': '/'
            }
        }
    };

#### # vue.config.js Configuration Example

If you are using Vue CLI, you can adjust the Webpack Dev Server by modifying `vue.config.js`. Below is a configuration example:

    // vue.config.js
    module.exports = {
        devServer: {
            headers: {
                'Service-Worker-Allowed': '/'
            }
        }
    };

## # Special Request Address

In the `fetch` event listened by the Service Worker in the application layer, if the request address matches `__foxitwebsdk-syncmsg__`, please ignore this request directly. This is also mentioned in our example code (`examples/PDFViewCtrl/integrate-service-worker/src/service-worker.js`).

## # Common Questions and Troubleshooting

1. ServiceWorker Not Registered

   - Issue description: Unable to find the ServiceWorker registration results in the developer tools.

   - Possible reasons:

     1. The path is set incorrectly, and the Service Worker js request returns 404 or other errors.
     2. The browser does not support ServiceWorker.
     3. The HTTPS protocol is not enabled. For security reasons, ServiceWorker can only be used under the HTTPS protocol. An exception to this is localhost and 127.0.0.1, which do not require HTTPS.

   - Solutions:

     1. Check the Service Worker's registration code and path configurations.
     2. Check browser compatibility. For Service Worker compatibility, please refer to Can I use
        (opens new window).
     3. Enable HTTPS protocol.

2. ServiceWorker Registration Failed

   - Issue description：A prompt during registration indicates that the scope exceeds the max scope allowed. Example of error message:

           register a ServiceWorker for scope ('http://localhost:9899/') with script ('http://localhost:9899/lib/MessageWorker.js?b=http://localhost:9899/__foxitwebsdk-syncmsg__'): The path of the provided scope ('/') is not under the max scope allowed ('/lib/'). Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.

   - Possible reasons: The registration path or scope of the Service Worker is not set correctly. It should be noted that the max scope allowed by the Service Worker depends on the location of the Service Worker script itself (refer to: MDN
     (opens new window)).

   - Solutions:

     1. Understand the scope rules of Service Worker.
     2. Check the registration code and scope settings of Service Worker.
     3. Adjust the scope of Service Worker, that is, modify the scope
        (opens new window) parameter.
     4. Adjust the `Service-Worker-Allowed` HTTP response header to allow a larger scope.
     5. Check the path of Service Worker script and server configuration.

3. ServiceWorker Registration Indicates Unsupported MIME Type

   - Issue description: A prompt during registration indicates an unsupported MIME type ('***/***'). Example of error message:

       Failed to register a ServiceWorker for scope ('http://localhost:5173/assets/') with script ('http://localhost:5173/assets/service-worker.js'): The script has an unsupported MIME type ('text/html').

   - Possible reasons: The registration path of the ServiceWorker is incorrect or the target file does not exist.
   - Solutions:
     1. Check the registration path of the ServiceWorker.
     2. Check if the target file exists.
     3. Check the server configuration.

4. ServiceWorker Registration Shows SSL Certificate Error

   - Issue Description: When registering, an error prompt appears: "An SSL certificate error occurred when fetching the script." Example of error message:

       SecurityError: Failed to register a ServiceWorker for scope ('https://your-domain.com') with script ('https://your-domain.com/path/to/foxit/sdk/lib/MessageWorker.js?b=https://your-domain.com/__foxitwebsdk-syncmsg__&h=0.6849802719940401'): An SSL certificate error occurred when fetching the script.

   - Possible Cause: The HTTPS certificate has expired or is invalid.
   - Solution: Please promptly update and correctly configure your HTTPS certificate to ensure it is valid.

5. Browser Forced Refresh Prevents ServiceWorker from Intercepting Requests

   - Issue Description: Refer to the MDN: ServiceWorkerContainer: controller property
     (opens new window) documentation, which describes that after a user performs a forced refresh (Shift + Refresh) of the page, the service worker loses control of the page, and the `navigator.serviceWorker.controller` property is set to null.
   - Possible Cause: The built-in `MessageWorker.js` of the SDK has automatically handled this issue. If your ServiceWorker code does not reuse MessageWorker.js and does not handle this situation, this phenomenon may occur.
   - Solution:
     1. After successfully registering the serviceWorker, check if `navigator.serviceWorker.controller` is null.
     2. If it is null, send a message to the ServiceWorker via the registration
        (opens new window).active.postMessage method, for example, the message we send is: `{id: 'clientsClaim'}`.
     3. Listen for this message in the ServiceWorker context and call the `clients.claim()` method after receiving the message.

   Here is a simple example:

   main.js:

       const registration = await navigator.serviceWorker.register(
           "service-worker.js" /* ... more options */
       );
       if (!navigator.serviceWorker.controller && registration.active) {
           function onControllerChange() {
               navigator.serviceWorker.removeEventListener(
                   "controllerchange",
                   onControllerChange
               );
               if (navigator.serviceWorker.controller) {
                   resolve(true);
               }
           }
           navigator.serviceWorker.addEventListener(
               "controllerchange",
               onControllerChange
           );
           registration.active.postMessage({ id: "clientsClaim" });
       }
       const pdfViewer =  new PDFViewCtrl.PDFViewer({
          messageSyncServiceWorker: {
              registration: registration,
          }
       }

   service-worker.js

       // ......
       self.addEventListener("message", (e) => {
           if (e.data?.id === "clientsClaim") {
               // Set the current service worker as the controller for all clients within its scope
               clients.claim();
           }
       });

## # ServiceWorker Use Cases

Foxit PDF SDK for Web uses ServiceWorker to optimize the user's interactive experience and solve interaction-related issues. Currently, ServiceWorker is utilized to handle local font access authorization, form filling functionality, and execution of JavaScript scripts for form fields and dynamic XFA elements in the user interface.