This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Concepts related to

This article focuses on how to achieve a simple micro channel small program without trace buried point scheme, so the conceptual knowledge of buried point do not do too much interpretation, we can Google. There are only two key issues we need to understand here.

What is a burial point

Buried point is a term in the field of data acquisition. Simply put, it is used to track some specific behaviors and events of users and record and report them. Common burial points are divided into three categories:

  • Manual embedding (code embedding)
  • Automatic embedding point (traceless embedding point)
  • Visual burial point

The effect of buried point

I think it will be clearer in terms of users and their products:

  • For users: The collected data can be analyzed to provide accurate information push and personalized recommendation to users. Of course, the behavioral information of users can also improve the operation experience of the product.
  • For products: analyze the existing problems of products and provide follow-up optimization ideas by analyzing the user’s dwell time on each page and the interactive nodes and other information.

    Reference link: xw.qq.com/partner/viv…

In fact, from the concept, the tracking of information and the reporting of information are completely independent. So we can design these two parts independently.

Ok, next is hand touch time!

The implementation process

The overall train of thought

First of all we need to know that in fact a small wechat program is aAppAnd then thisAppThere are multiplePage(page) andComponent(Custom components).What’s interesting is that bothAppRegister, orPageandComponentThe registration of each method is passed in the corresponding parameters(options)(This form can also be seen in the figure above)

Let’s take the Page registration as an example, and write the registration method in the figure in a different way to fit our description.

const options = {
  data: {
    msg: 'Hello World',},// User-defined events
  bindViewTap() {
    wx.navigateTo({
      url: '.. /logs/logs'}); },// Page has its own life cycle
  onLoad() {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true}); }}};// Register the current Page, which is completed by passing the corresponding parameters into the Page method provided by wechat
Page(options);
Copy the code

The App and Component registers are similar, if you are interested.

So so so so!We can rewrite itApp,Page,ComponentThese three methods to implement itoptionsSome method and lifecycle monitoring on the body (Yeah, that’s kind of what it means. You know what I mean.)

Ok, with the general idea in mind, let’s first summarize and then start doing:

  1. To design atrackerCome to the rightApp,Page,ComponentMethod to override
  2. To design areporterRecord the monitored event data and send it to the server

Tracker

Before we start to knock code to confirm that we realize how to use this buried point, only to determine a correct way of use, after the opening can be supplemented according to our corresponding function.

Under normal circumstances, if we want to modify the native method of wechat applet, we need to import our rewrite method in its entry file app.js to achieve this purpose

// app.js
import init from './track/index';

init({
  ak: 'minapp-001'.url: 'http://baidu.com'.autoTrack: {
    appLaunch: false.appHide: false.appShow: false.pageShow: false.pageHide: false.pageUnload: false.onShare: false,},// other
});
Copy the code

The config is tentative, and various configuration parameters can be added according to specific requirements. Here, we only set three attributes to ensure simplicity

  • Ak: ensure the unique value of the buried point program
  • Url: indicates the address of the server to which the obtained information needs to be uploaded
  • AutoTrack: indicates the solution for enabling full buried points. The corresponding field is set totrueEnable automatic burying information capture

Once we know how to use it, we can use it to determine how our code will unfold.

1. init

// Save the three native methods for later use
const collector = {
  oldApp = App,
  oldPage = Page,
}
Copy the code
const init = (config) = > {
  / / generated cid
  if(! storage.get('cid')) {
    storage.set('cid', getUUID());
  }
  // Initialize the user's custom configuration. Store is a global data warehouse.
  if(config ! = =undefined) store.set('config', config);

  // Override the App&Page method
  App = (options) = > collector.oldApp(proxyAppOptions(options));
  Page = (options) = > collector.oldPage(proxyPageOptions(options));
};
Copy the code

As you can see here, we’ve implemented the most critical step in our init method, which is rewriting the App and Page methods.

One thing to note here is that we need to make it clear that the purpose of overriding the method is to get some properties and methods of the options argument passed to it. Therefore, we will rewrite his options here, and then pass the rewritten results into the native App and Page methods for execution.

The Collector we defined above is intended to hold these native methods for easy invocation here. (Of course these attributes are not the only ones in the Collector, but some additional information will be collected later.)

2. proxyAppOptions

So let’s take a look at what the proxyAppOptions method does

/** * Overwrite App options *@param {*} Options The original options parameter *@returns The new options parameter */
const _proxyAppOptions = (options) = > {
  // Inject a manual burying method into the App
  options.$ta = {
    // Call track directly from getApp()
    track: $ta.track.bind(reporter),
    // The visitor access UID defaults to 0, and users need to manually update their user IDS after logging in
    login: (uid) = > this.login(uid),
  };

  // launch event listener
  options.onLaunch = useAppLaunch(options.onLaunch);
  // onShow event listener
  options.onShow = useAppShow(options.onShow);
  // onHide event listener
  options.onHide = useAppHide(options.onHide);

  return options;
};
Copy the code

UseAppLaunch; useAppShow; useAppLaunch; useAppShow; Let’s take a look at the implementation of these hooks, which are as simple as adding some logic to collect data at their own buried points

/ * * = = = = = = = = = = = = = = = = = = = = App event broker = = = = = = = = = = = = = = = = = = = = = = = = * /
export const useAppLaunch = (oldOnLunch) = >
  _proxyHooks(oldOnLunch, function () {
    const data = {
      event: 'appLaunch',
      path,
      title,
      timemap,
    };
    $ta.track('devices', data);
  });

export const useAppShow = (oldOnShow) = >
  _proxyHooks(oldOnShow, function () {
    const data = {
      event: 'appShow'}; $ta.track('devices', data);
  });

export const useAppHide = (oldOnHide) = > {
  _proxyHooks(oldOnHide, function () {
    const data = {
      event: 'appHide'}; $ta.track('devices', data);
  });
};

/** * proxies the original method and executes the callback function *@param {*} Fn requires the proxy method *@param {*} Cb Callback */ to be executed
function _proxyHooks(fn = function () {}, cb) {
  return function () {
    // If the callback exists
    if (cb) {
      cb.apply(this.arguments);
    }
    // Execute the original function
    fn.apply(this);
  };
}
Copy the code

This method is used to implement the original callback, which is then passed in by proxyHooks. In this callback, we need to add some data that we need to embed.

$ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta: $ta

3. proxyPageOptions

In fact, the page part and the above app do the same thing, is to do some processing for the life cycle, but page page in addition to the life cycle, there will be a lot of custom events, so we can take a look at this.

The custom events here are usually just click events.

// Page's original declaration cycle collection
const PAGE_LIFE_METHOD = [
  'onLoad'.'onShow'.'onReady'.'onHide'.'onUnload'.'onPullDownRefresh'.'onReachBottom'.'onShareAppMessage'.'onShareTimeline'.'onAddToFavorites'.'onPageScroll'.'onResize'.'onTabItemTap'.'onSaveExitState',];/** * Rewrite the Page options argument *@param {*} Options The original options parameter *@returns The new options parameter */
const proxyPageOptions = (options) = > {
  // ...

  // Customize event listening
  for (let prop in options) {
    // Make sure it is a function and not a native lifecycle function
    if (
      typeof options[prop] == 'function' &&
      !PAGE_LIFE_METHOD.includes(prop)
    ) {
      // Override custom methods on optionsoptions[prop] = usePageClickEvent(options[prop]); }}return options;
};
Copy the code

The pageClickEvent hook does this. The pageClickEvent hook does this. The pageClickEvent hook does this

/** * listen for page click events *@param {*} OldEvent native custom page events */
export const pageClickEvent = (oldEvent) = >
  _proxyHooks(oldEvent, function (e) {
    if (e && e.type === 'tap') {
      $ta.track('event', {
        event: 'pageClick'.// ...}); }});Copy the code

Reporter

What this part does is encapsulate a request method that sends the traced information to the server. But here are a few things to consider:

  1. Buried network requests should not preempt the original event request, i.e. the business request should be sent first
  2. Buried information should be sent in order to facilitate analysis, for example, the small program show information should be sent after hide
  3. When the network fluctuates, if the buried information fails to be sent, we should cache the data and wait for the next transmission to ensure the integrity of the information
import store from '.. /store';
import { storage } from '.. /.. /utils';
import platform from '.. /platform';
import qs from 'qs';

class Reporter {
  constructor() {
    // A queue of trace messages to send
    this.queue = [];
    this.timerId;
  }
  /** * Track buried data *@param {*} Data Indicates the data to be reported */
  track(type, data = {}) {
    // Add some public information fields
    data.t = type;

    this.queue.push(qs.stringify(data));

    if (!this.timerId) {
      // In order not to affect the normal business request, here send our buried point information delay
      this.timerId = setTimeout(() = > {
        this._flush();
      }, store.get('config').delay); }}/** * Performs tasks in the queue (sends trace information to the background) */
  _flush() {
    const config = store.get('config');

    // make a request when there is data in the queue
    if (this.queue.length > 0) {
      const data = this.queue.shift();
      platform.request({
        // Request an address
        url: config.url,
        // The timeout period
        timeout: config.request_timeout,
        method: 'POST'.header: { 'content-type': 'application/x-www-form-urlencoded' },
        data: {
          ak: config.ak,
          cid: storage.get('cid'),
          ns: store.get('networkType'),
          uid: storage.get('uid') | |0.data: Date.now(),
          data,
        },
        // When TODO fails to be sent, the message is saved to the storage
        success: () = > {},
        fail: ({ errMsg }) = > {
          console.error(errMsg);
        },
        complete: () = > {
          // Send the next message when the execution is complete
          this._flush(); }}); }else {
      this.timerId = null; }}}export default new Reporter();
Copy the code

conclusion

This article is just a simple implementation of the small program buried point scheme, from the overall framework to describe, a lot of details are not involved, we can discuss what problems.

This article originated from my official account. We can scan code to pay attention to the usual will send some strange things HH