Search code examples
javascriptwebviewwechat

Send message from WeChat mini-program to web-view


I'm building WeChat Mini-Program that on one of it's pages has web-view control. For example:

page.wxml

<web-view src="https://..." bindmessage="onWebViewMessage"></web-view>

page.js

const app = getApp();
Page({
    onWebViewMessage:function(e) {
         console.log(e);
    },
    onLoad:function() {
    }
});

In web-view an HTML page is loaded (index.html), that includes jweixin-1.3.2.js lib from WeChat, for connecting with WeChat API as well as connect to parent Mini-program. Page is empty, no DOM elements, just javascript that will execute when document is loaded.

It has it's javascript something like this:

index.js

document.addEventListener('DOMContentLoaded',function(){
    wx.miniProgram.postMessage({data:'test'});
});

I am able to post messages from this document to mini-program without issues. Also can send some mini-program navigation commands such as wx.miniProgram.navigateTo({url:'path/to/page'}); so all seems fine. I can also get callback in Mini-program when web-view has completed loading.

Question:

How can I post message from Mini-program to web-view? For example, to pass a string or an Object to the web-view.

I have been googling for hours and can't seem to find anyone doing it, but I can't believe it's just one-way communication possible.

Any help or idea is appreciated!


Solution

  • I have found an effective way to pass data from mini-program to web-view content, and it seems at this moment in time, this is the only possible way to do it.

    Mini-program

    1. Base64 module

    You will need to be able to convert normal String into Base64 string. Mini-program API has a method for converting byte array into base64 string, but that won't be usable for this purpose. So, create your own module that does that:

    File: lib/b64.js

    var string2base64 = function(str) {
        .... here put your js code for making b64 string ....
        return result;
    };
    
    module.exports = {
        string2base64
    };
    

    2. Page with Web-View

    In the page that has web-view control, prepare DOM element in wxml file like this:

    File: pages/xxx/index.wxml

    <web-view src="{{webURL}}" bindload="onWebLoad" binderror="onWebError"></web-view>
    

    Notice that src parameter is now bound to page's webURL property. Whenever page sets value to this property, will automatically be applied to the DOM elemenet.

    In file pages/xxx/index.js you will need to add base64 module:

    const b64 = require('../../lib/b64.js')
    

    note that require path may vary depending how you have setup your project

    and in page's data object, add webURL and webBaseURL properties, like this:

    Page({
        data: {
            webURL:'',
            webBaseURL:'https://your/web/app/url',
            messageQueue:[],
            messageQueueSize:0,
            .... other page properties go here ....
        },
        ..... rest of your page code goes here .....
    })
    

    Notice that webURL is set to be empty. This means that when page loads, an empty string will be set to DOM object by default.

    webBaseURL will explain just in a bit.

    messageQueue is an Array that will store pending messages to be sent to web-view. messageQueueSize is just Array length. Used for better performance, to avoid reading Array.length.

    3. Start Message Queue

    In onShow callback of the page, set webURL and start interval that will read messageQueue Array every 250ms. You can change the way this is done if you dislike using intervals, this was just simplest way to do theory test.

    onShow: function(){
        // This will start loading of the content in web-view
        this.setData({webURL: this.data.webBaseURL } );
    
        // Sends message from message queue to web-view
        let _this = this;
        setInterval(function(e) {
            if( _this.data.messageQueueSize < 1 ) return;
            _this.data.messageQueueSize --;
            let msg = _this.data.messageQueue.splice(0,1);
            _this.setData({webURL: _this.data.webBaseURL+"#"+msg});
        },250);
    }
    

    You can see that message is appended to web-view source (url) as a hash.

    webBaseURL is used to generate final URL with hash, that is then send to web-view.

    4. Add a Message to the Queue

    To create a message in message queue, just define following method in your page:

    addMessageToQueue: function(obj) {
        obj.unique = Math.round(Math.random()*100000);
        let msg = b64.string2base64(JSON.stringify(obj));
        this.data.messageQueue.push(msg);
        this.data.messageQueueSize++;
    }
    

    Whenever you call this method, just pass an Object with whatever properties you need it to have, and it will be converted into JSON string, then to base64 string, and finally appended to the message queue.

    unique property is added to make generated base64 result always different even if the rest of object properties are the same - I just needed this for the purpose of my project. You can ignore it / remove it if you do not need it.

    Since there's interval running and checking on the message queue, all messages added like this will be sent to web-view in the same order they were added to the queue.

    Now there's only one thing left - to add hash change listening in the HTML page we have loaded into the web-view:

    HTML Web-app

    1. Listen to hash change

    window.addEventListener("hashchange",function(e){
        let messageBase64 = window.location.hash.substr(1);
        let json = window.atob( messageBase64 );
        let data = JSON.parse(json);
        console.log("Received data from mini-program:",data);
    });
    

    Tested on Xiaomi Mi8 Pro. I am yet to test on other devices sold in China.

    Cheers!