从零打造专业级前端SDK(二):网络通信与策略模式

91 阅读3分钟

前言:在上一篇中,我们搭建了 SDK 的骨架。今天,我们要给它注入灵魂——让它能够感知环境,并将数据可靠地发送给服务器。

1. 挑战:复杂的浏览器环境

发送 HTTP 请求看似简单,但在 SDK 开发中却暗藏玄机:

  • 页面关闭时:用户关闭页面的一瞬间,普通的 fetch 请求会被浏览器无情截断。
  • 兼容性:有的老旧浏览器不支持 fetch,甚至不支持 sendBeacon
  • 数据格式:后端可能要求 JSON,也可能要求 URL 参数。

如何优雅地解决这些问题?答案是:策略模式

2. 策略模式 (Strategy Pattern)

我们定义一个统一的接口 SenderStrategy,然后实现不同的发送策略。上层逻辑(Tracker)不需要关心具体是用什么发的,只需要调用 send()

2.1 定义接口

export interface SenderStrategy {
  send(events: TrackerEvent[]): Promise<void>;
}

2.2 策略一:FetchSender (主力军)

这是我们最常用的方式。现代浏览器支持 keepalive 属性,允许请求在页面卸载后继续存活。

src/core/Sender.ts

// src/core/Sender.ts
export class FetchSender implements SenderStrategy {
  async send(events: TrackerEvent[]): Promise<void> {
    await fetch(this.endpoint, {
      method: 'POST',
      body: JSON.stringify(events),
      keepalive: true // 🔥 关键:防止页面关闭时请求被杀
    });
  }
}

2.3 策略二:BeaconSender (断后)

navigator.sendBeacon 是专门为"埋点"设计的 API。它的特点是:可靠性极高,即使页面关闭也能发送成功。

export class BeaconSender implements SenderStrategy {
  async send(events: TrackerEvent[]): Promise<void> {
    // 🔥 坑点:必须用 Blob 包装,否则 Content-Type 默认是 text/plain
    const blob = new Blob([JSON.stringify(events)], {
      type: 'application/json',
    });
    navigator.sendBeacon(this.endpoint, blob);
  }
}

2.4 策略三:ImageSender (兜底)

对于不支持上述 API 的古董浏览器,我们祭出上古神器:1x1 像素 GIF

export class ImageSender implements SenderStrategy {
  async send(events: TrackerEvent[]): Promise<void> {
    const img = new Image();
    // 数据只能放在 URL 参数里,注意长度限制
    img.src = `${this.endpoint}?data=${JSON.stringify(events)}`;
  }
}

3. 智能化环境采集 (Context)

如果每次埋点都要业务方手动传 userAgenturl,那这个 SDK 就太难用了。
我们封装一个 getContext() 工具,自动采集环境信息。

// src/utils/Context.ts
export function getContext() {
  return {
    userAgent: navigator.userAgent,
    screen: `${window.screen.width}x${window.screen.height}`,
    url: window.location.href,
    timestamp: Date.now()
  };
}

4. 全链路集成与验证

最后,我们在 Tracker 初始化时,根据浏览器支持情况选择合适的 Sender。

// src/core/Tracker.ts
this.sender = new FetchSender(config.endpoint); // 默认策略

// track 方法中
const context = getContext();
const finalEvent = { ...event, context };
this.sender.send([finalEvent]);

验证时刻

为了验证全链路,我们用 Node.js + Express 写了一个简单的接收端:

// server/index.js
import express from 'express';
const app = express();
app.post('/track', (req, res) => {
  console.log('收到埋点:', req.body);
  res.sendStatus(200);
});
app.listen(3000);

运行 Demo,点击按钮,看着服务器终端打印出数据的那一刻,闭环完成了

5. 总结

通过这两篇文章,我们实现了一个麻雀虽小、五脏俱全的埋点 SDK。

  • 架构:TypeScript + Vite + 单例/外观模式。
  • 网络:策略模式 (Fetch/Beacon/Image) + 环境自动采集。

当然,这只是开始。在生产环境中,我们还需要考虑:

  • 消息队列:削峰填谷,合并发送。
  • 离线存储:断网重试 (IndexedDB)。

这些进阶话题,我们将在后续文章中继续探讨。