A preface

In the last article, “Call App in practice”, we introduced the implementation principle of call end technology and some practices.

Here is a review of the main content of the last article:

1. What is a call?

A picture to understand what is called terminal.

2. Call function

3. Call end technology

The technical architecture of the call terminal is shown below.


New problems faced

Although the current solution already supports basic call capabilities, it can be said that the call technology is moving from 0 to 1. However, over time, as more and more compatible business logic became available, the internal structure of the project became chaotic, difficult to maintain, and could not meet new business requirements, so it was decided to carry out a major refactoring.

After business feedback and investigation, the main problems are as follows:

  1. Apps in the group have not been fully covered, and platform compatibility needs to be improved.
  2. It is not aligned with industry solutions and cannot meet the higher requirements of the business.
  3. The surrounding ecology needs to be improved, and the business experience needs to be improved.

The above problems were sorted out and evaluated, and the objectives of this reconstruction were finally determined:

  1. Overall architecture upgrade, support evoke multiple apps, improve evoke compatibility.
  2. To standard industry solutions, improve existing capabilities.
  3. Arouse ecosystem improvement around App to improve business experience.

Iii project Reconstruction

The overall architecture

1. The old scheme and architecture

First, take a look at the original directory structure (in fact, a good base library project can guess the outline of the architecture just by looking at the directory structure).

|-src
  |-- callers
    |-- 58App.js
    |-- browser.js
    |-- qq.js
    |-- wechat.js
    |-- zzLike.js
  |-- core
    |-- base.js
    |-- index.js
  |-- libs
    |-- config.js
    |-- platform.js
    |-- utils.js
 index.js
Copy the code

The architecture diagram is as follows:

With the increase of business complexity, this run-time platform-based policy pattern + adapter pattern design architecture has the following disadvantages:

  • The operating platform is unpredictable. If the new runtime environment is added, a caller class must be added to support it, which is inconsistent with the open and close principle.
  • In the process of business expansion and cross-logic processing, the code volume will increase rapidly, which seriously affects the code volume of the base library.
  • The division of functions and responsibilities is not clear and definite. Each caller class needs to be dealt with separately. Overall maintenance cost of the project is high and mental burden will be increased.

When supporting a limited number of runtime platforms and evoking a single App, the above architecture design fully meets the requirements, and the above problems will not become prominent. However, with the increasing complexity and requirements of business, the disadvantages will become more and more prominent.

2. Industry solutions

This paper studies and compares some schemes and constructions of calling end in the community. The main contents are as follows:

project The characteristics of disadvantages star
Open source web – launch – app Clear division of functions and responsibilities, compatible with multiple platforms Parameter design is complex, internal packaging is not concise, and callback is not supported 678
Open source – callapp – lib Clear division of functions and responsibilities, friendly code organization, compatible with multiple platforms Only browser side evokes a single App scheme 1.8 K.
Article – Technology behind call terminal Some advanced schemes are introduced Lack of detail, no open source

It can be seen that the community open source solutions are different in design, but it can be seen that they all adopt the design idea of single responsibility principle and modular function. In terms of architecture design, the second one is more clear and concise, and relatively valuable for reference, so we choose to carry out secondary development based on this.

In fact, in the JS architecture design this idea in each major open source framework are in use, such as the community of Vue framework, its internal source function division, responsibility separation and modular do very perfect. Therefore, this kind of programming idea (architecture idea) is worth learning from.

3. Turn-around plan after upgrade

The business scenario of turning around is more complex than the industry scheme, requiring a line of code to support all App call terminals of the group.

This extremely user-friendly and non-intrusive usage means that all the business logic and configuration needs to be handled internally within the base library, with increasing complexity and architectural design requirements.

// Call base library usage method, one line of code, support group all APPS
// Path is the IP address generated by the internal IP address platform
new CallApp().start({path: ' '})
Copy the code

Combined with the characteristics of its own business scenarios and excellent solutions of the industry, the following architecture is finally precipitated, as shown in the figure.

The principle and idea of the design is to divide functions and responsibilities, abstract out each function module, and then integrate each module into a whole.

The whole (to complete all the work of human survival) is divided into different parts (division of labor), by different roles to complete these division of labor, and through the establishment of different parts of the mutual communication mechanism, so that these parts can be organically combined into a whole.

Technical considerations in refactoring

1. Optimize the operating environment judgment strategy

When making judgments about the runtime environment, you need to deal with the giant if else. The idea of optimization (means) : reasonable data structure + strategy mode thought.

Let’s see what it looks like before optimization.

if(isIos) {
  if (isWechat && isLow7WX) {
    / /...
  } else if (isLow9Ios) {
    // ...
  } else if(! supportUniversal && isBaidu) {// ...
  } else if(! supportUniversal && (isWeibo || isWechat)) {// ...
  } else if(! supportUniversal || isQQ || isQQBrowser || isQzone) {// ...
  } else if (isQuark) {
    // ...
  } else {
    // ...}}if(isAndroid) {
  / / to omit...
}
Copy the code

In general, the most common implementation code of strategy pattern in JS is as follows:

// Define the policy
const strategy = {
  'case1': handler1, 
  'case1': handler1 
};
// Match the policy and run it
if(case) {
  strategy[case] (); }Copy the code

However, this implementation of the policy pattern defined with Object does not meet the requirements of this scenario for the following reasons:

  • Key cannot be a reference type.
  • Object Key disorder.

1. The key to order is a Map. 2. However, there are also the following problems in this scenario:

  • Key is a function that requires multiple set operations.
  • If the key is not O(1), the advantage of Map cannot be exploited.
const map = new Map()
map.set(fn1, handler1)
map.set(fn2, handler2)
/ /...
// map.set(fnN, handlerN)
// Use it in the business process (algorithm), worst O(n)
for (let [key, val] of map) {
  if(key()) return val()
}
Copy the code

Finally, considering the business usage scenarios, the data structure of array + object is adopted, which can not only ensure the execution order, but also provide the judgment condition in the form of function, and can give more attributes.

Define an ordered set of runtime environment policies based on the single responsibility and on/off principle
// Default policy
export let tempIosPlatRegList = null;

export const getDefaultIosPlatRegList = (ctx) = >[{name: 'wxSub'.platReg: () = > (isWechat && isLow7WX),
    handler: (instance) = > { / /... }
  },
  {
    name: 'low9'.platReg: () = > isLow9Ios,
    handler: (instance) = > { // ... }
  },
  {
    name: 'bd'.platReg: () = >! ctx.supportUniversal && isBaidu,handler: (instance) = > { // ... }
  }
  // ...
]
// Provide access methods externally
export const getIosPlatRegList = (ctx) = > 
  tempIosPlatRegList || (tempIosPlatRegList = getDefaultIosPlatRegList(ctx))

// Provide extension methods externally
export const addIosPlatReg(ctx, item) {
  if(validPlatRegItem(item)) {
    const list = getDefaultIosPlatRegList(ctx)
    list.splice(-1.0, item)
    tempIosPlatRegList = [...list]
  }
  return tempIosPlatRegList
}
Copy the code
// It is used in the call end business process
// Matches the runtime platform and runs the matched function
if(isIos) {
  for(let item of iosPlatRegList) {
    try {
      if(item && item.regPlat()) {
        item.handler(ctx)
        break; }}catch (error) {
      logError(item, error)
    }
  }
}
if(isAndroid) {
  for(let item of androidPlatRegList) {
    try {
      if(item && item.regPlat()) {
        item.handler(ctx)
        break; }}catch (error) {
      logError(item, error)
    }
  }
}

Copy the code

Is the code looking at the code is much more clear and concise, and provides external extension ability, convenient for users to expand it, which is also a good follow the open and closed principle.

Insight: In fact, JS strategy mode implementation does not have to use the Object or class data structure to achieve, as long as the definition of a set of case and handler,

Then let the algorithm according to the specified case to run the corresponding logic, regardless of which way. Design criteria for reference to policy patterns: define a family of algorithms (business rules); Encapsulate each algorithm; This family of algorithms can be replaced interchangeably.

2. Introduce the hooks

AOP ideas should be heard by everyone, often used in front-end frameworks such as: hook functions or life cycle functions, in fact, is a practice of AOP ideas.

Hook functions have been introduced to make it easy for users to insert some custom logic in the corresponding position. Such as burying, checking or magic configuration and so on.

PS: in the call process, there is no need for hooks queue to be compatible with mixins, so publish subscribe (on, ON, EMIT) is not introduced to implement hooks, only the simplest function calls are used.

// introduce hooks for logical insertions, burying points, etc
callApp.start({
  path: ' '.// Start evoking hooks, exposing configurations for inspection or wizardry
  callStart(opts) {},
  // Call up the success hook
  callSuccess() {},
  // Call up the failed hook
  callFailed() {},
  // Start downloading hooks
  callDownload(){}})Copy the code

3. Optimize with bit operations

As we all know, there are zeros and ones in the computer world. In the processing of high-level languages, the processing efficiency of bitwise operation is also the highest, and the multiple judgments can be combined with binary and/or/non-operational features to simplify operations.

When judging currentApp and targetApp, bit operators are introduced, and part of the code is as follows:

/ / AppFlags tag
export const enum AppFlags {
  ZZ = 1,
  ZZSeller = 1 << 1,
  ZZHunter = 1 << 2,
  ZZSeeker = 1 << 3,
  WXMini = 1 << 4,
  NoZZ = (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4),}// targetAppFlag is to invoke the targetAppFlag
if (targetAppFlag & AppFlags.ZZ) {
  // ...
} else if(targetAppFlag & AppFlags.NoZZ) {
  // ...
} else {}

// The way to write flag before introducing it
- if (targetApp === 'zz') {-// -}else if(
-  targetApp === 'zzSeeKer' || 
-  targetApp === 'zzHunter' || 
-  targetApp === 'zzSeller' || 
-  targetApp === 'wxMini'-) {-// -}else {}

Copy the code

Iv More Practices

Restore after download

Restoration schemes are mainly divided into two types:

1. Store it on the clipboard

In the IOS terminal for the first download restore active page function, the main use is to copy to the clipboard scheme.

Failure hook will be triggered if the calling end fails or download hook will be triggered by clicking download, the agreed protocol content will be copied to the system clipboard. After downloading, the App will start to automatically read the clipboard content. If the protocol format of the content matches successfully, it will jump to the specified page to achieve page restoration.

The main job on the front end is to copy the protocol content to the clipboard. The code implementation is as follows:

const copyToClipboard = str= > {
  // Create the textarea element
  const el = document.createElement('textarea');
  el.value = str;
  el.setAttribute('readonly'.' ');
  el.style.position = 'absolute';
  el.style.left = '-9999px';
  // Add to the page
  document.body.appendChild(el);
  // Get the original Selected range
  const selected =
    document.getSelection().rangeCount > 0
      ? document.getSelection().getRangeAt(0)
      : false;
  // Select and copy
  el.select();
  document.execCommand('copy');
  / / remove the textarea
  document.body.removeChild(el);
  // Restore selected range
  if (selected) {
    document.getSelection().removeAllRanges();
    document.getSelection().addRange(selected); }};Copy the code

Page restore effect is as follows:

2. Upload the file to the server

A unique depplinkid is generated on the server. When the download is triggered, the server will request the specified link (url with Deeplinkid), and the server will return the download package link with deeplinkid information. Trigger a download installation to restore the active page.

// Concatenate deeplinkid to download API URL
const zzDownloadLink = `${downloadApi}? applinkId=The ${}&channelId=The ${}`

// Trigger the download
evokeDownload(zzDownloadLink)
Copy the code

The overall process is shown as follows:

SMS short link arousal

It would be nice if users could call up the App directly from a text message (after all, text messaging is also a popular communication tool).

With the support of universal Link protocol of IOS and App Links protocol of Android (based on HTTP scheme), it is also possible to invoke app with short SMS links.

After investigation and practice, it is not difficult to implement, just need to support the domain name of the short link service on the corresponding protocol and make a little configuration on the client.

The following is the implementation process of IOS SMS short link arousing App.

The short link evokes the following:

Call end data statistics

To facilitate the statistics of call end data (call end PV, call end success/failure/download rate, etc.), hooks and Lego embedded points can be used for call end data reporting and subsequent analysis.

Hooks + lego embed code:

import lego from './lego'

new CallApp({
   path: ' '.callStart() {
    lego.send({
      actiontype: 'START'.pagetype: 'ZZDOWNLOADH5'.backup: { channelId },
    })
  },
  callSuccess() {
    lego.send({
      actiontype: 'SUCCESS'.pagetype: 'ZZDOWNLOADH5'.backup: { channelId },
    })
  },
  callFailed() {
    lego.send({
      actiontype: 'FAILED'.pagetype: 'ZZDOWNLOADH5'.backup: { channelId },
    })
  },
  callDownload() {
    lego.send({
      actiontype: 'DOWNLOAD'.pagetype: 'ZZDOWNLOADH5'.backup: { channelId },
    })
  },
})
Copy the code

Through buried point data, we can make further analysis and statistics, such as the number of call end, system information UA, and so on, so as to improve the system.

Here’s an example:

Below is the buried point data for a period of time viewed on the LEGO platform:

Call the trigger Call the success Call the failure
678 359 238

A problem is found in the data, trigger > success + failure, so what is the cause? It can be speculated that the button is not shaken when the call terminal is triggered, so it can be repaired and perfected, so that a data-driven feedback loop can be formed.

Ecological construction in surrounding areas

Tool platform

The front-end tool platform can generate and evoke App long links, short links and short SMS links with one click, which is convenient for business use.

All jump platform

The unified hop platform is convenient for multiple apps to jump to target URLS for unified management. A URL can be a page address or a description of an action, such as a popover.

Middle landing page

Unified middle page, so that users can access the URL page in the form of access to evoke function, without modifying the project code to introduce evoke library, call end middle page support multiple apps.

Vi Open Source Construction

The project has been open source on Github, welcome to ridicule.

Github address: github.com/zhuanzhuanf…

Summary and prospect

After a lot of practice, in the end of the accumulated precipitation of some experience, but there is still a long way to go.

For example, the current call end success rate is only a very rough statistics. If the user triggers the universal Link call, it will redirect to the domain name bound to the Universal Link if the call fails. Such success/failure statistics are not available (the page is reconnected and the page stack disappears). Therefore, a full-link reporting system needs to be built together with the client side.

In addition, the page shared by the end is not closed loop with the backflow of the call end, so the hidden relationship between the sharing link and users cannot be counted, so it is impossible to make better algorithm recommendation.

That’s all for this post. Thanks for reading.


Finally, attached is a list of call side compatibility: