preface

As the App project currently developed adopts the H5+ native development mode, the H5 page opened in the App relies on the JS-Bridge provided by the App, so the browser cannot open the page normally.

As a front-end cutters, I often develop relevant pages according to UI drafts. In the process of dealing with page details, I cannot audit elements and view detailed parameters of elements through mobile phone debugging, which leads to wasting too much time to repeatedly debug page effects. In order to improve the development efficiency, we decided to develop a Chrome plug-in for Hybrid App. Because Chrome plug-in has the function of injecting scripts into web pages, it just makes the injected scripts realize the functions of JS-Bridge.

The development process

Before the development of Chrome plug-in, we must understand some basic knowledge, about Chrome plug-in walkthrough, there has been a summary and introduction of the big guys ([work] Chrome plug-in (extension) development of the whole walkthrough – Xiao Ming students), in the next development process, the use of the relevant configuration, will be introduced in detail.

Chrome-Hybrid App plugin version 1.0

Before coming to the project team, chrome-Hybrid App version 1.0 had been basically built by the predecessors.

The project structure

├ ─ ─ assets│ ├ ─ ├ ─ └ //│ ├ ─ ─ icon - 16. PNG│ ├ ─ ─ icon - 32. PNG│ └ ─ ─ icon - 64. The PNG├ ─ ─ js│ ├ ─ ─ the app│ │ ├ ─ ─ constants│ │ ├─ Event-action.js│ │ ├ ─ ├ ─ gene-type.js│ ├ ─ ├ ─ sci-imp│ ├─ Background exercises - background.js // Resident js or page│ ├ ─ ─ config. Js│ ├ ─ ─ constant. Js├ ─ └─ inject.js //├─ Manifest.json // Add-on Config File (Important)└─ popup.html // Click the plug-in icon popup windowCopy the code

In version 1.0, popup.html and background.js are not currently used. Mainfest.json is configured as follows:

{
 "name": "chrome-hybrid-app". "version": "1.0". "manifest_version": 2. "description": "It is used to connect communication between chrome and H5". "permissions": [// Plug-in permission List of allowed permissions "webRequest". "http://*/*/*". "https://*/*/*" ]. "icons": {  "16": "assets/images/icon-16.png". "32": "assets/images/icon-32.png". "64": "assets/images/icon-64.png"  },  "browser_action": {  "default_icon": "assets/images/icon-32.png". "default_popup": "popup.html"  },  "content_scripts"Inject the H5 page JS file {  "matches": [// matches all addresses "http://*/*/*". "https://*/*/*" ]. "js": [  "js/constant.js". "js/config.js". "js/inject.js"  ]  } ]. "web_accessible_resources"Allow the H5 page to access the resource list "js/app/constants/event-type.js". "js/app/constants/event-action.js". "js/app/js-bridge.js"  ] } Copy the code

JS – the realization of the Bridge

In the current development project,H5 can run normally. The key point is that it must realize the native protocol for obtaining user information, and H5 calls user information of native protocol so that the basic process can run on the H5 page.

Inject jS-Bridge flowchart
  • Within the window. The setSupportCommonType is H5 implementation method, main function is to initialize when mounted on the window, told H5 currently supported by native calls the original agreement.
  • Window.care.com monCallback is implemented in Native method, similarly mounted on the window, when the H5 page needs to call Native related functions and access to relevant data, H5 and incoming call the method name and parameters related to the original definition of agreement.
  • The main function of window.appEventHandler is to call the h5-related protocol callback after the native H5 calls the related protocol, and pass in the processed data.

Specific code implementation:

/ * ** The response page sends instructions to the App * @param {String} optionString {type, extras}
* /function commonCallback(optionString) {
 if(! optionString) { return;  }  const option = JSON.parse(optionString);  const { type = ' ' } = option;  const actionName = EVENT_ACTION_NAME[type];  const actionResponse = EVENT_ACTION_RESPONSE[actionName];   if (actionName && actionResponse) {  window.appEventHandler({ actionName, params: actionResponse });  } }  / * ** Gets the set of currently supported protocols* /function getSupportCommonType() {  const typeList = [];  const eventTypeObject = EVENT_TYPE;  for (let key in eventTypeObject) {  const type = eventTypeObject[key];  typeList.push(type);  }   return typeList; }  // 1. Initialize commonCallbackwindow.care = {  commonCallback }; // 2. Set the supported protocol typeconst supportCommonTypeList = getSupportCommonType(); window.setSupportCommonType(supportCommonTypeList); Copy the code

The realization of inject. Js

After completing the JS-Bridge implementation, we thought about how to inject into the H5 page. Content_scripts was introduced in the configuration file, and it felt like we could inject jS-bridg directly into the page.

 "content_scripts": [
  {
   "matches": [
    "http://*/*/*".    "https://*/*/*"
]. "js": [  "js/app/constants/event-type.js". "js/app/constants/event-action.js". "js/app/js-bridge.js"  ]  }  ] Copy the code

After deploying the plugin to Chrome (see the Chrome plugin guide link above for relevant deployment), we opened the H5 page and found that the page was not properly opened. Then we opened the console and found an error message.

Js-bridge displays an error message

The setSupportCommonType method is not mounted on the window, indicating that the jS-Bridge file cannot read the H5 page JS. In this case, we find that we cannot inject the Js-Bridge file directly. Content_scripts 插件 图 片

  • Functions and variables defined by the Web page cannot be accessed
  • Content scripts can read and modify the DOM of a displayed web page

Content scripts can access and modify the DOM of H5 pages, so we can use indirect injection. Basically, we can mount jS-Bridge on H5 pages by creating script tags, so that jS-Bridge can access methods related to H5 pages.

Note: web_accessibLE_resources sets the list of resources that the page can access, otherwise indirectly injected files cannot access the functions and variables defined by the page.

The concrete implementation is as follows:

/ * *Inject related JS files into a specific node* @param {String} file File name* @param {String} node Node name* /function injectJsBridgeScript(file, node) {  let parentNode = document.getElementsByTagName(node);  parentNode = parentNode && parentNode[0];  if(! parentNode) { return;  }   console.log('============ start inject js-bridge ============');  const scriptElement = document.createElement('script');  scriptElement.setAttribute('type'.'text/javascript');  scriptElement.setAttribute('src', file);  parentNode.appendChild(scriptElement); }  const jsBridgeFilePathList = JS_BRIDGE_FILE_PATH_LIST[CONFIG.appEnv] || []; for (let filePath of jsBridgeFilePathList) {  injectJsBridgeScript(chrome.extension.getURL(filePath), 'body'); } Copy the code

The main function of Chrome.extend. getURL is to convert the relative path of files in the extension installation directory into FQDN URL. So far, our Chrome-hybrid-app version 1.0 has been roughly completed. When opening the H5 page in the browser, the page is found to be normal. Can achieve basic functions.

defects

Js, event-action.js, and js-bridge.js), among which js-bridge.js file depends on variables of other files, there will be defects in inject indirect injection. If the JS-Bridge is loaded before the dependency file, an error message will appear indicating that the variable is not defined.

Js-bridge error message – dependency not loaded in time

After an error occurs, check the Network panel and find that the loading time of dependent files is too long.

Netword panel information

Solution:

  • The indirect injection file becomes one
  • Load on demand, through promises and so on

Improved point

Since we injected jS-Bridge file, all the relevant user information sent to H5 page was written in the file. For example, the user information contains token. Every time the user rewrites to log in, the user token will change, which will cause us to change the token every time when the token changes. The jS-Bridge configuration must be changed manually.

Considering the further improvement of work efficiency, plug-in optimization has several aspects:

  • Solve the above defects in the plug-in, sometimes load failure problem
  • Dynamically modify static information of JS-Bridge configuration through plug-in interface interaction
  • Plug-in integration front-end packaging tool Webpack, modify, compile, debug integration
  • The React framework is used to develop the plug-in interface

Chrome-Hybrid App version 2.0

Based on the defects and improvements of version 1.0, make improvements and improvements on this basis.

The project structure

├ ─ ─ dist│ ├ ─ ─ background. Js│ ├ ─ ─ the ICONS│ │ ├ ─ ─ icon - 16. PNG│ │ ├ ─ ─ icon - 32. PNG│ │ └ ─ ─ icon - 64. The PNG│ ├ ─ ─ the manifest. Json│ └ ─ ─ popup. HTML├ ─ ─ modules│ ├ ─ ─ the content - the script│ │ ├ ─ ─ config. Ts│ │ ├ ─ ─ constant. Ts│ │ └ ─ ─ index. Ts│ ├ ─ ─ inject│ │ ├ ─ ─ constants│ │ ├─ Event-Action│ │ ├ ─ ├ ─ ├ ─ garbage│ │ └ ─ ─ index. Ts│ └ ─ ─ popup│ ├ ─ ─ index. The TSX│ └ ─ ─ the SRC│ └ ─ ─ index. The TSX├ ─ ─ package. Json├ ─ ─ tsconfig. Json├ ─ ─ typings│ ├ ─ ─ image. Which s│ └ ─ ─ style. Which s├ ─ ─ webpack - config│ ├ ─ ─ loader. Js│ ├ ─ ─ plugin. Js│ └ ─ ─ webpack. Config. Js└ ─ ─ yarn. The lockCopy the code

The dist folder stores our packaged plug-ins. Among them, backgroud, ICONS, manifest and popup do not need to be packaged and can be used as the default configuration file. In version 2.0, popup is used to realize plug-in interface (click the plug-in icon, a pop-up box will appear), popup does not support inline JS, must be imported.

Modules stores directly injected files Content-script, indirectly injected files Inject, and popup page JS file popup. The three folders will be packed and compressed by Webpack to generate corresponding JS files. So Webpack packaging adopts multi-entry packaging, and monitor file changes, real-time generation of compiled files.

  entry: {
    popup: './modules/popup/index.tsx'.    inject: './modules/inject/index.ts'.    'content-script': './modules/content-script/index.ts'
  },
 resolve: {  extensions: ['.tsx'.'.ts'.'.js']  },  output: {  filename: '[name]/index.js'. path: path.resolve(__dirname, '.. /dist/')  } Copy the code

Loader and Plugin configurations related to WebPack are not described here. For details, please refer to the official WebPack documents.

Where mainfest.json is changed, permissions are added to storage (for storing interactive data), tabs (to refresh the page after user interaction), and single files for both direct and indirect injection files. Mathches set the H5 page address to avoid blind injection.

 "permissions": [
  "webRequest".  "http://*/*/*".    "https://*/*/*".    "storage". "tabs" ]. "content_scripts": [  {  "matches": [ // Match the H5 page address]. "js": [  "content-script/index.js"  ]  } ]. "web_accessible_resources": [  "inject/index.js"  ] Copy the code

Process design

In the process of considering plug-in interaction, we must understand the communication rules between Content-Script and Inject. There are various communication modes, and I adopt the mode that is convenient to use and meets the corresponding requirements.

Content-script communicates with Inject

Content-script communicates with Popup in a special way. When Popup receives user interaction data, it stores it in the plug-in, and then Content-Scirpt takes the plug-in’s local data.

Version 2.0 Process

We will improve our previous modules according to the flow chart.

In step 1, we inject Content-script, which listens for an indirect injection of reject that has finished loading. Chrome.storage.local. get gets the method for storing data locally.

function getLocalToken() {
  chrome.storage.local.get('bear-token'.function(result) {
    const bearToken = result['bear-token']? result['bear-token'] : ' ';
    window.postMessage({"token": bearToken }, The '*');
  });
}  window.addEventListener("message".function (e) {  if (e.data.flag) {  getLocalToken();  } }, false); Copy the code

In step 2, you inject reject indirectly, similar to the previous version 1.0.

In step three, reject passes the loading message to The Content-script, while listening for the data that the Content-Script passes.

window.postMessage({"flag": true }, The '*');

window.addEventListener("message".function (e) {
  if (e.data.token) {
    EVENT_ACTION_RESPONSE.token = e.data.token;
 window.setSupportCommonType(supportCommonTypeList);  } }, false); Copy the code

In steps 4 and 5, Content-script listens for reject and immediately passes the local message to Reject. Reject receives the message, modiates the default configuration file, calls H5 methods, and finishes loading the H5 page.

In step 6 and step 7, the user operates the Popup screen, stores the interactive information in the Chrome plug-in, and refreshes the page to start the page injection process.

  const handleInjectToken = (token: string = ' ') = > {    chrome.storage.local.set({ 'bear-token': token }, function () {
      chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.tabs.update(tabs[0].id, { url: tabs[0].url });
      });
 handleClickSuccessSnackBar();  setToken(' ');  setPhone(' ');  });  }; Copy the code
Popup interface

Popup supports cross-domain by default, so the user data is obtained by requesting background interface when logging in to the mobile phone number.

The development summary

In the process of the development of the 2.0 version of the plug-in, I have a simple understanding of Chrome plug-in development and process, and found that the current work efficiency is greatly improved, but also enrich my knowledge. At present, there may be some defects in the whole process, which will be maintained and extended in the future work.

This article is formatted using MDNICE