前端抽象化,打破框架枷锁:web的多端通讯抽象

多端通讯

开发前端应用,与其他平台的通讯是常见需求,比如以下场景:

  • iframe嵌套网页
  • webview和APP或者小程序通讯
  • websocket通讯
  • 开发桌面端应用的IPC通讯
  • webworker通讯

这些通讯机制的核心在于消息的发送与接收,通常遵循以下模式:

// 发送消息
port1.postMessage('message', data)
// 接收消息
port2.onMessage = (data) => {
  console.log(data)
}

尽管不同通讯方式的 API 和配置参数各异,其本质均为点对点消息传递。因此,抽象出一个通用的多端通讯模块,不仅能统一接口,还能提升开发效率和代码可维护性。
吸取之前《前端抽象化,打破框架枷锁》系列的经验,本文提出一种更精简、更实用的设计方案

核心内容

核心模块

  1. 桥对象(Bridge):桥对象是一个抽象接口,封装了多端通讯的核心功能,包括:
  • 消息发送与接收
  • 两个生命周期
  • 监听器注册与删除

该接口需根据具体场景(如 iframe、WebSocket 等)实现不同的桥实例,以适配多样化的通讯需求。

  1. 桥监听器(BridgeListener):桥监听器以统一格式定义事件监听,包含:
  • 事件名称
  • 回调函数
    其设计保持极简,仅提供基础功能,开发者可根据需求扩展高级特性,如优先级或过滤条件。
  1. 桥数据对象(BridgeDTO):用统一格式来定义消息的数据,这部分基本上是不用改动的,除非你特殊需要
  2. 桥工厂(BridgeFactory):桥工厂采用工厂模式,负责根据通讯场景动态创建桥对象。相比适配器模式,工厂模式提供了更高的灵活性,便于开发者定制特定场景的桥实现。某些功能则只有工厂模式情况下,可以高质量的维护,比如:
  • 桥的生命周期管理
  • 桥的配置的预处理:比如iframe的targetOrigin
  • 依赖注入和组合

宏观的整体依赖关系

uml diagram

TS的抽象源码

/**
 * 桥接类型枚举
 */
export enum BridgeEnum {
    /** WebSocket通信 */
    WEBSOCKET = 'websocket',
    /** iframe通信 */
    IFRAME = 'iframe',
    /** WebView通信 */
    WEBVIEW = 'webview'
}

/**
 * 桥接数据传输对象
 * @template T - 请求数据类型
 * @template O - 响应数据类型
 */
export interface BridgeDTO<T = any, O = any> {
    /** 桥接类型 */
    bridgeType: BridgeEnum;
    /** 请求数据 */
    payload?: T;
}

/**
 * 桥接状态枚举
 */
export enum BridgeStatus {
    DISCONNECTED = 'disconnected',
    CONNECTED = 'connected'
}

import { BridgeDTO, BridgeEnum, BridgeStatus } from "./entities";

/**
 * 桥接监听器接口,用于处理接收到的消息
 */
export interface BridgeListener {
    /** 监听器类型 */
    listentType: BridgeEnum;
    /** 消息处理回调函数 */
    onListent: (data: BridgeDTO) => void;
}

/**
 * 桥接接口,定义通信的基本操作
 */
export interface Bridge {
    /** 通信目标对象 */
    target: any;
    /** 监听器集合 */
    listeners: Record<string, BridgeListener[]>;
    /** 连接状态 */
    status: BridgeStatus;
    /** 连接钩子 */
    connectHooks:Set<Function>
    /** 断开连接钩子 */
    disconnectHooks:Set<Function>
    /**
     * 建立连接
     * @returns {void}
     */
    connect(): void;

    /**
     * 添加连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addConnectHook(hook: Function): void;
    /**
     * 添加断开连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addDisconnectHook(hook: Function): void;

    /**
     * 断开连接
     * @returns {void}
     */
    disconnect(): void;

    /**
     * 发送数据
     * @template T - 请求数据类型
     * @template O - 响应数据类型
     * @param {BridgeDTO<T, O>} data - 要发送的数据
     * @returns {void}
     */
    send<T, O>(data: BridgeDTO<T, O>): void;

    /**
     * 添加消息监听器
     * @param {BridgeListener} listener - 监听器对象
     * @returns {void}
     */
    addListener(listener: BridgeListener): void;

    /**
     * 移除消息监听器
     * @param {BridgeListener} listener - 要移除的监听器对象
     * @returns {void}
     */
    removeListener(listener: BridgeListener): void;
}

import { Bridge } from './Bridge';
import { WebSocketBridge } from './WebSocketBridge';
import { IframeBridge } from './IframeBridge';
import { WebViewBridge } from './WebViewBridge';
import { BridgeEnum } from './entities';

/**
 * 桥接工厂类,用于创建和管理不同类型的桥接实例
 */
export class BridgeFactory {
    /** 桥接实例缓存 */
    private static bridges: Map<BridgeEnum, Bridge> = new Map();

    /**
     * 创建桥接实例
     * @param {BridgeEnum} type - 桥接类型
     * @param {Record<string,any>} options - 创建选项
     * @returns {Bridge} 桥接实例
     * @throws {Error} 不支持的桥接类型
     */
    public static createBridge(type: BridgeEnum, options: Record<string,any>): Bridge {
        let bridge = this.bridges.get(type);
        if (!bridge) {
            switch (type) {
                case BridgeEnum.WEBSOCKET:
                    bridge = new WebSocketBridge(options.url);
                    break;
                case BridgeEnum.IFRAME:
                    bridge = new IframeBridge(options.target, options.targetOrigin);
                    break;
                case BridgeEnum.WEBVIEW:
                    bridge = new WebViewBridge(options.nativeBridge);
                    break;
                default:
                    throw new Error(`Unsupported bridge type: ${type}`);
            }
            this.bridges.set(type, bridge);
        }
        return bridge;
    }

    /**
     * 获取桥接实例
     * @param {BridgeEnum} type - 桥接类型
     * @returns {Bridge | undefined} 桥接实例
     */
    public static getBridge(type: BridgeEnum): Bridge | undefined {
        return this.bridges.get(type);
    }

    /**
     * 销毁桥接实例
     * @param {BridgeEnum} type - 桥接类型
     * @returns {void}
     */
    public static destroyBridge(type: BridgeEnum): void {
        const bridge = this.bridges.get(type);
        if (bridge) {
            bridge.disconnect();
            this.bridges.delete(type);
        }
    }
}

import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';

/**
 * iframe桥接实现类,用于通过iframe进行跨窗口通信
 * @implements {Bridge}
 */
export class IframeBridge implements Bridge {
    /** iframe窗口对象 */
    target: Window;
    /** 监听器集合,按桥接类型分组 */
    listeners: Record<string, BridgeListener[]>;
    /** 当前连接状态 */
    status: BridgeStatus = BridgeStatus.DISCONNECTED;
    /** 连接钩子集合 */
    connectHooks: Set<Function> = new Set();
    /** 断开连接钩子集合 */
    disconnectHooks: Set<Function> = new Set();
    /** 目标窗口的源 */
    private targetOrigin: string;

    /**
     * 创建iframe桥接实例
     * @param {Window} target - iframe窗口对象
     * @param {string} targetOrigin - 目标窗口的源
     */
    constructor(target: Window, targetOrigin: string) {
        this.target = target;
        this.targetOrigin = targetOrigin;
        this.listeners = {};
        this.setupMessageListener();
    }

    /**
     * 建立iframe连接
     * @returns {void}
     */
    connect(): void {
        this.status = BridgeStatus.CONNECTED;
        this.connectHooks.forEach(hook => hook());
    }

    /**
     * 断开iframe连接
     * @returns {void}
     */
    disconnect(): void {
        window.removeEventListener('message', this.handleMessage);
        this.status = BridgeStatus.DISCONNECTED;
        this.disconnectHooks.forEach(hook => hook());
    }

    /**
     * 发送数据到iframe窗口
     * @template T - 请求数据类型
     * @template O - 响应数据类型
     * @param {BridgeDTO<T, O>} data - 要发送的数据
     * @returns {void}
     */
    send<T, O>(data: BridgeDTO<T, O>): void {
        this.target.postMessage(JSON.stringify(data), this.targetOrigin);
    }

    /**
     * 添加连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addConnectHook(hook: Function): void {
        this.connectHooks.add(hook);
    }

    /**
     * 添加断开连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addDisconnectHook(hook: Function): void {
        this.disconnectHooks.add(hook);
    }

    /**
     * 添加消息监听器
     * @param {BridgeListener} listener - 监听器对象
     * @returns {void}
     */
    addListener(listener: BridgeListener): void {
        const type = listener.listentType;
        if (!this.listeners[type]) {
            this.listeners[type] = [];
        }
        this.listeners[type].push(listener);
    }

    /**
     * 移除消息监听器
     * @param {BridgeListener} listener - 要移除的监听器对象
     * @returns {void}
     */
    removeListener(listener: BridgeListener): void {
        const type = listener.listentType;
        const listeners = this.listeners[type];
        if (listeners) {
            const index = listeners.indexOf(listener);
            if (index > -1) {
                listeners.splice(index, 1);
            }
        }
    }

    /**
     * 设置消息监听器
     * @private
     * @returns {void}
     */
    private setupMessageListener(): void {
        window.addEventListener('message', this.handleMessage.bind(this));
    }

    /**
     * 处理接收到的消息
     * @private
     * @param {MessageEvent} event - 消息事件对象
     * @returns {void}
     */
    private handleMessage(event: MessageEvent): void {
        if (event.origin !== this.targetOrigin) {
            return;
        }

        try {
            const data = JSON.parse(event.data) as BridgeDTO;
            this.notifyListeners(data);
        } catch (error) {
            console.error('Failed to parse message:', error);
        }
    }

    /**
     * 通知所有监听器接收到的消息
     * @private
     * @param {BridgeDTO} data - 接收到的数据
     * @returns {void}
     */
    private notifyListeners(data: BridgeDTO): void {
        const typeListeners = this.listeners[data.bridgeType];
        if (typeListeners) {
            typeListeners.forEach(listener => {
                listener.onListent(data);
            });
        }
    }
}

import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';

/**
 * WebView桥接实现类,用于通过原生WebView进行通信
 * @implements {Bridge}
 */
export class WebViewBridge implements Bridge {
    /** WebView目标对象 */
    target: any;
    /** 监听器集合,按桥接类型分组 */
    listeners: Record<string, BridgeListener[]>;
    /** 当前连接状态 */
    status: BridgeStatus = BridgeStatus.DISCONNECTED;
    /** 连接钩子集合 */
    connectHooks: Set<Function> = new Set();
    /** 断开连接钩子集合 */
    disconnectHooks: Set<Function> = new Set();
    /** 原生桥接对象 */
    private nativeBridge: any;

    /**
     * 创建WebView桥接实例
     * @param {any} nativeBridge - 原生桥接对象
     */
    constructor(nativeBridge: any) {
        this.nativeBridge = nativeBridge;
        this.listeners = {};
        this.setupNativeBridge();
    }

    /**
     * 建立WebView连接
     * @returns {void}
     */
    connect(): void {
        this.status = BridgeStatus.CONNECTED;
        this.connectHooks.forEach(hook => hook());
    }

    /**
     * 断开WebView连接
     * @returns {void}
     */
    disconnect(): void {
        this.nativeBridge = null;
        this.status = BridgeStatus.DISCONNECTED;
        this.disconnectHooks.forEach(hook => hook());
    }

    /**
     * 发送数据到原生WebView
     * @template T - 请求数据类型
     * @template O - 响应数据类型
     * @param {BridgeDTO<T, O>} data - 要发送的数据
     * @returns {void}
     */
    send<T, O>(data: BridgeDTO<T, O>): void {
        if (this.nativeBridge) {
            this.nativeBridge.postMessage(JSON.stringify(data));
        } else {
            console.error('Native bridge is not available');
        }
    }

    /**
     * 添加连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addConnectHook(hook: Function): void {
        this.connectHooks.add(hook);
    }

    /**
     * 添加断开连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addDisconnectHook(hook: Function): void {
        this.disconnectHooks.add(hook);
    }

    /**
     * 添加消息监听器
     * @param {BridgeListener} listener - 监听器对象
     * @returns {void}
     */
    addListener(listener: BridgeListener): void {
        const type = listener.listentType;
        if (!this.listeners[type]) {
            this.listeners[type] = [];
        }
        this.listeners[type].push(listener);
    }

    /**
     * 移除消息监听器
     * @param {BridgeListener} listener - 要移除的监听器对象
     * @returns {void}
     */
    removeListener(listener: BridgeListener): void {
        const type = listener.listentType;
        const listeners = this.listeners[type];
        if (listeners) {
            const index = listeners.indexOf(listener);
            if (index > -1) {
                listeners.splice(index, 1);
            }
        }
    }

    /**
     * 设置原生桥接
     * @private
     * @returns {void}
     */
    private setupNativeBridge(): void {
        if (this.nativeBridge) {
            this.nativeBridge.onMessage = (message: string) => {
                try {
                    const data = JSON.parse(message) as BridgeDTO;
                    this.notifyListeners(data);
                } catch (error) {
                    console.error('Failed to parse native message:', error);
                }
            };
        }
    }

    /**
     * 通知所有监听器接收到的消息
     * @private
     * @param {BridgeDTO} data - 接收到的数据
     * @returns {void}
     */
    private notifyListeners(data: BridgeDTO): void {
        const typeListeners = this.listeners[data.bridgeType];
        if (typeListeners) {
            typeListeners.forEach(listener => {
                listener.onListent(data);
            });
        }
    }
}

import { Bridge, BridgeListener } from './Bridge';
import { BridgeDTO, BridgeEnum, BridgeStatus } from './entities';

/**
 * WebSocket桥接实现类,用于通过WebSocket进行通信
 * @implements {Bridge}
 */
export class WebSocketBridge implements Bridge {
    /** WebSocket连接目标 */
    target: WebSocket;
    /** 监听器集合,按桥接类型分组 */
    listeners: Record<string, BridgeListener[]>;
    /** 当前连接状态 */
    status: BridgeStatus = BridgeStatus.DISCONNECTED;
    /** 连接钩子集合 */
    connectHooks: Set<Function> = new Set();
    /** 断开连接钩子集合 */
    disconnectHooks: Set<Function> = new Set();
    /** WebSocket服务器URL */
    private url: string;

    /**
     * 创建WebSocket桥接实例
     * @param {string} url - WebSocket服务器URL
     */
    constructor(url: string) {
        this.url = url;
        this.listeners = {};
    }

    /**
     * 建立WebSocket连接
     * @returns {void}
     */
    connect(): void {
        if (this.status === BridgeStatus.CONNECTED) {
            return;
        }

        try {
            this.target = new WebSocket(this.url);

            this.target.onopen = () => {
                this.status = BridgeStatus.CONNECTED;
                this.connectHooks.forEach(hook => hook());
            };

            this.target.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data) as BridgeDTO;
                    this.notifyListeners(data);
                } catch (error) {
                    console.error('Failed to parse message:', error);
                }
            };

            this.target.onerror = (error) => {
                console.error('WebSocket error:', error);
            };

            this.target.onclose = () => {
                this.status = BridgeStatus.DISCONNECTED;
                this.disconnectHooks.forEach(hook => hook());
            };
        } catch (error) {
            console.error('Failed to create WebSocket:', error);
            this.status = BridgeStatus.DISCONNECTED;
        }
    }

    /**
     * 断开WebSocket连接
     * @returns {void}
     */
    disconnect(): void {
        if (this.target) {
            this.target.close();
            this.status = BridgeStatus.DISCONNECTED;
            this.disconnectHooks.forEach(hook => hook());
        }
    }

    /**
     * 发送数据到WebSocket服务器
     * @template T - 请求数据类型
     * @template O - 响应数据类型
     * @param {BridgeDTO<T, O>} data - 要发送的数据
     * @returns {void}
     */
    send<T, O>(data: BridgeDTO<T, O>): void {
        if (this.target && this.target.readyState === WebSocket.OPEN) {
            this.target.send(JSON.stringify(data));
        } else {
            console.error('WebSocket is not connected');
        }
    }

    /**
     * 添加连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addConnectHook(hook: Function): void {
        this.connectHooks.add(hook);
    }

    /**
     * 添加断开连接钩子
     * @param {Function} hook - 钩子函数
     * @returns {void}
     */
    addDisconnectHook(hook: Function): void {
        this.disconnectHooks.add(hook);
    }

    /**
     * 添加消息监听器
     * @param {BridgeListener} listener - 监听器对象
     * @returns {void}
     */
    addListener(listener: BridgeListener): void {
        const type = listener.listentType;
        if (!this.listeners[type]) {
            this.listeners[type] = [];
        }
        this.listeners[type].push(listener);
    }

    /**
     * 移除消息监听器
     * @param {BridgeListener} listener - 要移除的监听器对象
     * @returns {void}
     */
    removeListener(listener: BridgeListener): void {
        const type = listener.listentType;
        const listeners = this.listeners[type];
        if (listeners) {
            const index = listeners.indexOf(listener);
            if (index > -1) {
                listeners.splice(index, 1);
            }
        }
    }

    /**
     * 通知所有监听器接收到的消息
     * @private
     * @param {BridgeDTO} data - 接收到的数据
     * @returns {void}
     */
    private notifyListeners(data: BridgeDTO): void {
        const typeListeners = this.listeners[data.bridgeType];
        if (typeListeners) {
            typeListeners.forEach(listener => {
                listener.onListent(data);
            });
        }
    }
}

图示

流程图

uml diagram

细节的类图

核心其实集中在了一个Bridge对象上,重点的数据对象则单独定义.再结合一个工厂模式来创建桥接实例.

你可以右键下面这个图,在新的标签页中打开,这样可以放大和拖动的查看

uml diagram

尾声

通过 UML 类图,我清晰地呈现了一个多端通讯模块的架构设计。该方案以 Bridge 接口为核心,通过工厂模式和标准化数据结构,实现了通讯逻辑的抽象与统一。无论是 WebSocket、iframe 还是 Webview,开发者只需调用统一的接口,就能快速实现跨端通讯,极大地提升了开发效率和代码可维护性。

未来,我们可以进一步扩展该模块,例如支持更多的通讯协议(如 RTC 或 Service Worker),或集成错误重试和超时机制,以应对更复杂的业务场景。

如果有更好建议,或者有你的常见对应实现想要分享,欢迎来到我的githubopen in new window中提交你的issue,或者pr,共同进步.

如果你想复制粘贴使用,也是我的github地址open in new window

最后更新日期 6/24/2025, 10:29:18 AM