The implementation of real-time communication between webview and h5 via postMessage

Original: https://pantao.parcmg.com/pre...

When making React Native applications, if you need to embed H5 pages in App, H5 and App can communicate in real time through the PostMessage function of Webview. However, in the applet, although a WebView component is also provided, when postMessage communication is made, it is in the official documentA very perverted description is given:

When a Web page postMessages to an applet, it triggers and receives a message at a specific time (applet backoff, component destruction, sharing).E.detail = {data}, data is an array of parameters for multiple postMessages

It is clear that no matter how many times we post Message from H5 page, the applet will not receive it unless:

  1. The user did a fallback to the previous page
  2. Component Destroy
  3. User clicked on Share

Actually, I'm not completely right about that. What the government said is applet retreat, not user retreat. After my actual measurement, it is really clear that people have expressed it. The retreat initiated by WeChat official SDK is also completely feasible:

wx.miniProgram.navigateBack()

General idea

From the analysis and measurements above, we can see that in order to achieve communication that can be accomplished without user action, the third scenario is one that we don't need to consider at all, so consider scenarios 1 and 2 carefully.

Method 1: Backward

When we want to send data to the applet through a web page and go back to the previous page, we can call wx.miniProgram.navigateBack() once after wx.miniProgram.postMessage, and the applet operates as follows:

  1. Processing postMessage information
  2. Back to the previous page

We do some special things when working with postMessage to save this data

Second way: component destruction

This is the most appropriate way for me to get the data for the applet while keeping it on the current page. It only needs to destroy the webview once. The general process is:

  1. Applet postMessage
  2. Applet navigateTo directs applet pages to a special page
  3. The special page of the applet immediately falls back to the page where the webview is located
  4. In the onShow of the page where the WebView is located, do a process, destroy the webview, and open it again
  5. Trigger onMessage to get data
  6. H5 page opened again

Although this method is abnormal, it can at least get the data in real time and keep it on the current H5 page. The only problem to solve is that the H5 page needs to be cached well before doing this whole set of operations, otherwise, the data of H5 will be emptied after opening it again.

One way: by backing out, data is submitted to the applet and passed to the previous page of the webview

It's a simple way to do that. Now we'll create two new pages:

sandbox/canvas-by-webapp/index.js

const app = getApp();

Page({
  data: {
    url: '',
    dimension: null,
    mime: '',
  },
  handleSaveTap: function() {
    wx.navigateTo({
      url: '/apps/browser/index',
      events: {
        receiveData: data => {
          console.log('receiveData from web browser: ', data);
          if (typeof data === 'object') {
            const { url, mime, dimension } = data;
            if (url && mime && dimension) {
              this.setData({
                url,
                dimension,
                mime,
              });

              this.save(data);
            }
          }
        }
      }
    })
  },

  save: async function({ url, mime, dimension }) {
    try {
      await app.saveImages([url]);
      app.toast('Save successfully!');
    } catch (error) {
      console.log(error);
      app.toast(error.message || error);
    }
  },
});

The core of the above code is the events parameter inside the wx.navigateTo call, which is used to communicate with/apps/browser/index pages and receive data.

apps/browser/index.js

I omitted the vast majority of code that is not relevant to this article and saved the three most important ones:

Page({
  onLoad() {
    if (this.getOpenerEventChannel) {
      this.eventChannel = this.getOpenerEventChannel();
    }
  },
  handleMessage: function(message) {
    const { action, data } = message;
    if (action === 'postData') {
      if (this.eventChannel) {
        this.eventChannel.emit('receiveData', data);
      }
    }
  },

  handlePostMessage: function(e) {
    const { data } = e.detail;
    if (Array.isArray(data)) {
      const messages = data.map(item => {
        try {
          const object = JSON.parse(item);
          this.handleMessage(object);
          return object;
        } catch (error) {
          return item;
        }
      });

      this.setData({
        messages: [...messages],
      });
    }
  },
})

In fact, the onLoad method uses the getOpenerEventChannel method, which has been available since the WeChat SDK version 2.7.3, to create an event communication channel with the previous page, which we will use in handleMessage.

HandlePostMessage is the way that bindmessage is put on the Webview to handle messages coming from postMessage on H5 page. Because the applet sends messages from multiple postMessages together, the difference with other Webviews is that we get an array: e.detail..data, the purpose of handlePostMessage is to iterate through the array, take out each message, and hand it over to the handleMessage.

handleMessage takes out the message.action and message.data after it gets the message object (* Note here that this is a data structure that we designed in H5 and you can design your own structure in your own project). Actions do different things, and I'm working on it hereYes, when action =='postData', the message channel this.eventChannel obtained from getOpenerEventChannel pushes the data to the previous page, that is, /sandbox/canvas-by-webapp, but you do not need to execute navigateBack yourself, because this needs to be handed over to the H5 page to execute.

Implementation of H5 Page

My H5 main purpose is to use the html2canvas library to generate Canvas diagrams (I can't help but it's too cumbersome to draw in the applet), but instead of this discussion, we'll just generate the canvas pictures, convert them to base64 text, and do the following:

wx.miniProgram.postMessage({
  data: JSON.stringify({
    action: 'postData',
    data: 'BASE 64 IMAGE STRING'
  })
});

wx.miniProgram.navigateBack()

Immediately after postMessage, navigateBack() triggers a fallback, which also triggers the bindmessage event.

Real-time communication using destroyed webview

Next, I'll start this article with the focus, a more abnormal way, but I didn't think of a better way, so you'll have to communicate.

H5 Page Change

wx.miniProgram.postMessage({
  data: JSON.stringify({
    action: 'postData',
    data: 'BASE 64 IMAGE STRING'
  })
});

wx.miniProgram.navigateTo('/apps/browser/placeholder');

The H5 page simply changes wx.miniProgram.navigateBack() to wx.miniProgram.navigateTo('/apps/browser/placeholder'), and everything else is handled by the applet first.

/apps/browser/placeholder

The function of this page is really simple. When you open it, do a little bit of work and immediately go back to the previous page (that is, the page where the webview is located).

Page({
  data: { loading: true },
  onLoad(options) {

    const pages = getCurrentPages();

    const webviewPage = pages[pages.length - 2];

    webviewPage.setData(
      {
        shouldReattachWebview: true
      },
      () => {
        app.wechat.navigateBack();
      }
    );
  },
});

Let's look at it line by line:

const pages = getCurrentPages();

This takes you to the page stack of the current whole applet. Since this page only allows you to come from the WebView page of the applet, its previous page must be the page where the WebView is located:

const webviewPage = pages[pages.length - 2];

Once you get the webviewPage page object, call its method setData to update a value:

    webviewPage.setData(
      {
        shouldReattachWebview: true
      },
      () => {
        app.wechat.navigateBack();
      }
    );

When the shouldReattachWebview value is true, it means you need to attach the webview again. The event for this page has now been completed and you are back to the page where the webview is located.

apps/browser/index.js page

I also only keep the core code, specific logic, and I write it directly into the code.

Page({
  data: {
    shouldReattachWebview: false, // Do you need to attach the webview component again
    webviewReattached: false,     // Has the webview been attach ed once
    hideWebview: false            // Whether to hide webview components
  },
  onShow() {
    // If the webview needs to be re attach ed 
    if (this.data.shouldReattachWebview) {
      this.setData(
        {
          // Hide webview
          hideWebview: true,
        },
        () => {
          this.setData(
            {
              // Show it immediately after hiding, then complete a webview destruction and get the data in the postMessage
              hideWebview: false,
              webviewReattached: true,
            },
            () => {
              // Once you get the data, process canvasData
              this.handleCanvasData();
            }
          );
        }
      );
    }
  },
  // This method is triggered when the webview is destroyed
  handlePostMessage: function(e) {
    const { data } = e.detail;
    if (Array.isArray(data)) {
      const messages = data.map(item => {
        try {
          const object = JSON.parse(item);
          this.handleMessage(object);
          return object;
        } catch (error) {
          return item;
        }
      });

      this.setData({
        messages: [...messages],
      });
    }
  },
  // Processing each message
  handleMessage: function(message) {
    const {action, data} = message
    // If saveCanvas action
    if (action === 'saveCanvas') {
      // Cache data into Snap first
      const { canvasData } = this.data;
      // app.checksum is my own encapsulation method, calculating the checksum of any data, and I use it as the key
      // This ensures that the same data will only be processed once
      const snapKey = app.checksum(data);
      // Only unprocessed data needs to be retrieved
      if (canvasData[snapKey] !== true) {
        if (canvasData[snapKey] === undefined) {
          // Cache data into `snap`
          // This is also my own way of encapsulating data that can be cached and read only once
          app.snap(snapKey, data);
          // Set the snapKey field in canvasData to `false`
          canvasData[snapKey] = false;
          this.setData({
            canvasData,
          });
        }
      }
    }
  },
  // When the webview is re attach ed, the canvas data is saved in the snap.
  handleCanvasData: async function handleCanvasData() {
    const { canvasData } = this.data;
    // Get all the key s from canvasData and filter to the data you've already processed
    const keys = Object.keys(canvasData).filter(key => canvasData[key] === false);

    if (keys.length === 0) {
      return;
    }

    for (let i = 0; i < keys.length; i += 1) {
      try {
        const key = keys[i];
        const { url } = app.snap(key);
        // Save the url (Base64 characters) to the album by encapsulating it yourself
        const saved = await app.saveImages(url);
        // Update canvasData object
        canvasData[key] = true
        this.setData({
          canvasData
        })
        console.log('saved: ', saved);
      } catch (error) {
        app.toast(error.message);
        return;
      }
    }
  },
})

The corresponding index.wxml file is as follows:

<web-view src="{{src}}" wx:if="{{src}}" bindmessage="handlePostMessage" wx:if="{{!hideWebview}}" />

Process Review and Summary

  1. Open webview page, open h5
  2. h5 page generates canvas diagram and converts to base64 character
  3. Send base64 to applet via wx.miniProgram.postMessage
  4. Call wx.miniProgram.navigateTo to direct the page to a special page
  5. On special pages, set shouldReattachWebview to true on the page where the webview is located
  6. Return to the page where the webview is located on a special page
  7. The onShow event on the page where the webview is located is triggered
  8. Detects whether shouldReattachWebview is true, if true, in the onShow event
  9. Setting hideWebview to true causes the destruction of web-view components
  10. handlePostMessage is triggered and handleMessage is handled step by step after all message s are resolved
  11. handleMessage finds an event with action =='saveCanvas', gets data
  12. Calculates the checksum from the data, caches the data with checksum as the key, and saves the checksum in the canvasData object
  13. At this point hideWebview is reset to false by setData in the callback of setData in onShow, web-view is re attach ed, H5 page is reloaded
  14. After the webview attach es again, this.handleCanvasData is triggered.
  15. handleCanvasData detects whether canvas data needs to be saved and, if so, saves and modifies the canvasData state

The whole process looks tedious, but it's good to write. The most important thing is to note that the data is heavy. WeChat's postMessage always gets all the data from H5 pages opened to closed.

Keywords: Front-end JSON SDK React

Added by pchytil on Mon, 19 Aug 2019 20:05:16 +0300