Gahing's blog Gahing's blog
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Gahing / francecil

To be best
首页
知识体系
  • 前端基础
  • 应用框架
  • 工程能力
  • 应用基础
  • 专业领域
  • 业务场景
  • 前端晋升 (opens new window)
  • Git
  • 网络基础
  • 算法
  • 数据结构
  • 编程范式
  • 编解码
  • Linux
  • AIGC
  • 其他领域

    • 客户端
    • 服务端
    • 产品设计
软素质
  • 面试经验
  • 人生总结
  • 个人简历
  • 知识卡片
  • 灵感记录
  • 实用技巧
  • 知识科普
  • 友情链接
  • 美食推荐 (opens new window)
  • 收藏夹

    • 优质前端信息源 (opens new window)
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前端基础

  • 应用框架

  • 工程能力

  • 应用基础

  • 专业领域

    • 服务端

    • 跨端技术

      • H5

        • H5导航栏
        • H5常见问题和解决方案
        • bridge设计
          • 需求
          • 代码
        • fastclick 原理剖析与踩坑记录
        • iOS 上拉下拉 webview 出现空白的问题
        • 移动端响应式布局
        • 移动端强制横竖屏
        • 机型分级方案总结
      • Hybrid 基建需要做哪些?
      • PWA

      • 高性能动态化跨端方案设计
      • 端能力

    • Web IDE

    • 中后台

    • 动效渲染

    • 可视化

    • 埋点监控

    • 多媒体

    • 桌面技术

    • 游戏互动

    • 编辑器

    • 虚拟化与容器化

    • 设计系统

  • 业务场景

  • 大前端
  • 专业领域
  • 跨端技术
  • H5
gahing
2022-07-03
目录

bridge设计草稿

# 需求

  • 支持插件
  • 类型提示完善且正确

# 代码

主要模块

import Debug from 'debug';
import promiseCallback, {
  CallbackWithPromise,
} from '../utils/promise-callback';
import { callNative, pushEventQueue } from './call-native';
import NetworkEventManager from './EventManager';
import BridgePlugin, { IBridgePlugin } from './plugins/plugin';
import Request, { TriggerRequest } from './Request';
import Response from './Response';

const debug = Debug('base-bridge');
/**
 * 客户端像服务端传递的结果
 */
export interface Result {
  code: number; // 状态码
  msg?: string; // 错误原因,如有
  data?: any; // 可选,抛出异常时可能没有 data 值
}

export interface CallbackResponse {
  result: Result;
  /** 端能力回调的关联 id ,前端透传给 native */
  callbackId: string;
}

export interface EventResponse {
  result: Result;
  /** 监听的客户端事件 */
  event: string;
}

export type CallbackMapValue =
  | BridgeCallback
  | CallbackWithPromise
  | Array<BridgeCallback>;

export type BridgeCallback = (result: Result) => void;

export class Bridge {
  _networkEventManager: NetworkEventManager;

  private _plugins: IBridgePlugin[] = [];

  constructor() {
    this._networkEventManager = new NetworkEventManager();
  }

  /**
   * 处理端能力回调
   * @param res
   * @returns
   */
  async _handleCallbackFromNative(res: CallbackResponse) {
    const { callbackId, result } = res;

    const requestResult = this._networkEventManager.getRequest(callbackId);
    if (!requestResult) {
      return;
    }
    const { request, callback } = requestResult;
    const response = new Response(this, request, result);
    request._response = response;
    await this.callPlugins('afterCall', response);

    // call 处理
    this._networkEventManager.forgetEvent(callbackId);
    (callback as BridgeCallback)(response.result);
  }

  /**
   * 处理事件监听的回调
   * @param res
   * @returns
   */
  async _handleEventFromNative(
    res: EventResponse,
    triggerRequest?: TriggerRequest,
  ) {
    const { event } = res;
    let { result } = res;
    result = await this.callPluginsWithValue(
      'on',
      result,
      event,
      triggerRequest,
    );
    const handlers = this._networkEventManager.getEventListeners(event);
    if (!handlers) {
      return;
    }
    handlers.forEach(handler => {
      handler(result);
    });
  }

  call(event: string, params?: any): Promise<Result>;
  call(event: string, params: any, handler: BridgeCallback): void;
  /**
   * web: 主动调用
   * @param event
   * @param params
   * @param callback
   */
  // eslint-disable-next-line consistent-return
  async call(event: string, params?: any, callback?: BridgeCallback) {
    const res = await this.callPluginsWithValue('beforeCall', {
      params,
      event,
    });
    // eslint-disable-next-line no-param-reassign
    params = res.params ?? params;
    // eslint-disable-next-line no-param-reassign
    event = res.event ?? event;

    // 若传递 callback 参数,则不使用 promise
    const usePromise = !callback;
    let handler: CallbackMapValue;
    if (usePromise) {
      handler = promiseCallback();
    } else {
      handler = callback;
    }

    const request = new Request(this, event, params);
    const { requestId } = request;
    this._networkEventManager.storeRequest(requestId, request, handler);

    await this.callPlugins('call', request);

    // 若未被拦截,继续调用端能力
    if (!request.isIntercepted) {
      callNative(request.event, {
        callbackId: requestId,
        params: request.params,
      });
    }

    if (usePromise) {
      return (handler as CallbackWithPromise).promise;
    }
  }

  /**
   * 自定义事件和回调
   * @param {string} event Event name.
   * @param {Function} listener
   */
  on(event: string, listener: BridgeCallback) {
    this._networkEventManager.storeEvent(event, listener);
    return this;
  }

  /**
   * 和on相对,解除注册
   * @param {string} event Event name.
   * @param {Function} listener
   */
  off(event: string, listener?: BridgeCallback) {
    this._networkEventManager.forgetEvent(event, listener);
    return this;
  }

  /**
   * @param {string} event
   * @param {Object} params
   */
  async trigger(event: string, result: Result) {
    const res = await this.callPluginsWithValue('beforeTrigger', {
      result,
      event,
    });
    // eslint-disable-next-line no-param-reassign
    result = res.result ?? result;
    // eslint-disable-next-line no-param-reassign
    event = res.event ?? event;

    const triggerRequest = new TriggerRequest(this, event, result);

    await this.callPlugins('trigger', triggerRequest);

    if (!triggerRequest.isTriggerUseLocal) {
      // 未进行本地转发,则将事件推送至客户端处理
      pushEventQueue(event, result);
    }
  }

  use(plugin: BridgePlugin) {
    if (typeof plugin !== 'object' || !plugin._isBridgePlugin) {
      console.error(
        `Warning: Plugin is not derived from BridgePlugin, ignoring.`,
        plugin,
      );
      return this;
    }
    if (!plugin.name) {
      console.error(
        `Warning: Plugin with no name registering, ignoring.`,
        plugin,
      );
      return this;
    }
    this._plugins.push(plugin);
    debug('plugin registered', plugin.name);
    return this;
  }

  eject(plugin: BridgePlugin) {
    const index = this._plugins.findIndex(p => p === plugin);
    if (index === -1) {
      debug('plugin not found', plugin.name);
    } else {
      debug('plugin eject', plugin.name);
      this._plugins.splice(index, 1);
    }
    return this;
  }

  /**
   * Call plugins sequentially with the same values.
   * Plugins that expose the supplied property will be called.
   *
   * @param prop - The plugin property to call
   * @param values - Any number of values
   * @private
   */
  private async callPlugins(prop: string, ...values: any[]) {
    for (const plugin of this.getPluginsByProp(prop)) {
      await plugin[prop](...values);
    }
  }

  private async callPluginsWithValue(
    prop: string,
    value: any,
    ...extra: any[]
  ) {
    for (const plugin of this.getPluginsByProp(prop)) {
      const newValue = await plugin[prop](value, ...extra);
      if (newValue) {
        // eslint-disable-next-line no-param-reassign
        value = newValue;
      }
    }
    return value;
  }

  /**
   * Get all plugins that feature a given property/class method.
   *
   * @private
   */
  private getPluginsByProp(prop: string): IBridgePlugin[] {
    return this._plugins.filter(plugin => prop in plugin);
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257

导出模块

import Debug from 'debug';
import { Bridge } from './BaseBridge';

// const prefix = `d.`;

// const BridgeEvent = {
//   getAppInfo: `${prefix}getAppInfo`,
//   getUserInfo: `${prefix}getUserInfo`,
// };
// const as = Object.keys(BridgeEvent);
const debug = Debug('core');

export type IBridge = Pick<
  Bridge,
  'call' | 'on' | 'off' | 'trigger' | 'use' | 'eject'
>;

function singleton(): IBridge {
  if (typeof window !== 'undefined' && window.JSBridge) {
    debug('hit cache');
    return window.JSBridge;
  }
  const bridge = new Bridge();

  const singletonBridge: IBridge = {
    call: bridge.call.bind(bridge),
    on: bridge.on.bind(bridge),
    off: bridge.off.bind(bridge),
    trigger: bridge.trigger.bind(bridge),
    use: bridge.use.bind(bridge),
    eject: bridge.eject.bind(bridge),
  };

  debug('create');

  window.JSBridge = singletonBridge;

  window.JSBridge._handleCallbackFromNative =
    bridge._handleCallbackFromNative.bind(bridge);
  window.JSBridge._handleEventFromNative =
    bridge._handleEventFromNative.bind(bridge);
  return singletonBridge;
}
const bridge = singleton();

export default bridge;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
编辑 (opens new window)
上次更新: 2024/09/01, 23:56:56
H5常见问题和解决方案
fastclick 原理剖析与踩坑记录

← H5常见问题和解决方案 fastclick 原理剖析与踩坑记录→

最近更新
01
浅谈代码质量与量化指标
08-27
02
快速理解 JS 装饰器
08-26
03
Vue 项目中的 data-v-xxx 是怎么生成的
09-19
更多文章>
Theme by Vdoing | Copyright © 2016-2024 Gahing | 闽ICP备19024221号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式