npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

edu-signalr-sdk

v2.2.2

Published

教学服务实训平台跟踪上报 SDK —— 通过 SignalR 长连接实现实训时长跟踪、真实实训行为检测与浏览器指纹防作弊

Readme

edu-signalr-sdk

教学服务实训平台跟踪上报 SDK。通过 SignalR 长连接实现:

  • 学生实训累计时长跟踪
  • 真实实训行为检测(鼠标、键盘、触摸、滚动、页面可见性)
  • 浏览器设备指纹防作弊(基于 FingerprintJS 专业指纹库)
  • 自动化工具/模拟器检测
  • Token 过期自动刷新
  • 教辅端远程踢人下线

安装

npm install edu-signalr-sdk

一键接入(推荐)

SDK 内置 fastGo 快捷方法,自动完成 URL 参数解析、自动登录、SignalR 连接建立、事件注册与销毁绑定。

import { fastGo, FastGoError } from "edu-signalr-sdk";

const { sdk, loginRet } = await fastGo({
  hubUrl: "https://你的教辅端域名/hubs/traincloud",
  autoLoginApi: "http://localhost:3031/api/training/auto-login",
  srRefreshTokenApi: "http://localhost:3031/api/training/refresh-token",
  interceptors: {
    request: (params) => params,
    response: (res) => res,
  },
});

fastGo 启动流程

URL 参数解析 → interceptors.request 加工 → autoLoginApi 登录
    → interceptors.response 加工响应
    → 提取 signalr Token → 创建 SignalR 连接
    → 返回 { sdk, loginRet }

若服务端未返回 signalr(或 signalr.accessToken 为空),则跳过 SignalR 建连步骤,sdknullloginRet 仍可用。

fastGo 完整配置

import { fastGo, FastGoError } from "edu-signalr-sdk";

try {
  const { sdk, loginRet } = await fastGo({
    // ==================== 必填 ====================

    /** SignalR Hub 地址 */
    hubUrl: "https://教辅端域名/hubs/traincloud",

    /** 自动登录接口(获取用户信息 + SignalR Token) */
    autoLoginApi: "/api/training/auto-login",

    /** SignalR Token 刷新接口 */
    srRefreshTokenApi: "/api/training/refresh-token",

    /** 请求/响应拦截器 */
    interceptors: {
      // 对自动从 URL 解析的参数进行加工
      request: (params: Record<string, any>) => params,
      // 对登录接口返回的数据进行加工
      response: (res: any) => res,
    },

    // ==================== 可选配置 ====================

    /**
     * 判断是否需要初始化 SignalR 连接。
     * 返回 false 时仅执行登录流程(loginRet 仍可用),不建立长连接。
     * @param params URL 查询参数
     */
    requireSrFn: (params) => params.role === "student",

    /** 心跳间隔(毫秒),默认 30000 */
    heartbeatInterval: 30000,
    /** 挂机判定阈值(毫秒),默认 120000 */
    idleTimeout: 120000,
    /** 最大重连次数,默认 3 */
    maxReconnectTimes: 3,

    // ==================== 事件回调 ====================

    onConnected: () => console.log("已连接"),
    onDisconnect: () => console.log("已断开"),
    onReconnecting: () => console.log("正在重连..."),
    onReconnected: () => console.log("重连成功"),
    onKick: (msg) => {
      alert(msg);
      window.location.href = "/login";
    },
    onReconnectFail: () => alert("连接异常,请重新进入"),

    // ==================== 自定义 Hub 方法注册 ====================

    hubMethods: {
      "OnScoreUpdate": (data) => updateScoreBoard(data),
      "OnClassNotice": (msg) => showNotice(msg),
      "OnReward": (payload) => showReward(payload),
    },

    // ==================== 行为开关 ====================

    /** 是否自动绑定 beforeunload 销毁,默认 true */
    autoDestroy: true,
  });

  // ===== 使用返回的 SDK 实例 =====
  if (sdk) {
    console.log(sdk.state); // "Connected"
    // 调用服务端 Hub 方法(兼容后期扩展)
    await sdk.invoke("SubmitAnswer", { questionId: 1, answer: "A" });
    // 动态注册新事件
    sdk.on("OnNewFeature", (data) => console.log(data));
  }

  // ===== 使用登录返回信息 =====
  const { userName, accessToken, refreshToken, tokenExpires, refreshTokenExpires } = loginRet;
  // 将 Token 写入状态管理...

} catch (reason) {
  if (reason instanceof FastGoError) {
    switch (reason.code) {
      case "LOGIN_FAILED":
        console.error("登录失败,服务端返回:", reason.data);
        break;
      case "UNKNOWN_ERROR":
        console.error("未知错误:", reason.data);
        break;
    }
  }
}

说明fastGo 内部基于 EduSignalrSdk.create() 实现。如需更细粒度的控制(如手动管理 Token 获取流程),请使用下方「标准接入」方式。


业务系统集成示例(Vue 2)

以下是一个实训系统入口页面的完整实现,展示 SDK 在真实项目中的典型用法:

<template>
  <div class="init-container">
    <div class="init-content">
      <div class="logo-container">
        <div class="logo-icon"></div>
      </div>
      <h1 class="title">网店数据分析平台</h1>
      <div class="loading-container">
        <div class="loading-spinner">
          <div class="spinner-ring"></div>
          <div class="spinner-ring"></div>
          <div class="spinner-ring"></div>
        </div>
        <p class="loading-text">{{ message }}</p>
        <div class="loading-dots">
          <span></span><span></span><span></span>
        </div>
      </div>
      <div class="footer-text">生意参谋 - 数据驱动决策</div>
    </div>
  </div>
</template>

<script lang="ts">
import user from "@/store/module/user";
import { fastGo, FastGoError } from "edu-signalr-sdk";

export default {
  data() {
    return { message: "正在初始化系统" };
  },
  async mounted() {
    try {
      // ===== 一键启动 SDK =====
      const ret = await fastGo({
        hubUrl: "http://192.168.31.206:5001/hubs/traincloud",
        autoLoginApi: "http://localhost:3031/api/training/auto-login",
        srRefreshTokenApi: "http://localhost:3031/api/training/refresh-token",
        interceptors: {
          request: (pdata: Record<string, any>) => pdata,
          response: (res: any) => res,
        },
        onConnected: () => console.log("已连接"),
        onDisconnect: () => console.log("已断开"),
        onKick: (msg) => {
          alert(msg);
          window.location.href = "/login";
        },
      });

      ret.sdk && console.log(ret.sdk.state); // "Connected"

      if (ret.loginRet) {
        const { userName, accessToken, refreshToken, tokenExpires, refreshTokenExpires } = ret.loginRet;
        user.setToken(accessToken);
        user.setUserName(userName);
        user.setTraining(true);
        this.$router.push({ name: this.$config.homeName });
      } else {
        this.$router.push("/");
      }
    } catch (reason) {
      // ===== 类型安全的错误处理 =====
      if (reason instanceof FastGoError) {
        switch (reason.code) {
          case "LOGIN_FAILED":
            this.$Message.error({ content: reason.data.message });
            break;
          case "UNKNOWN_ERROR":
            this.$Message.error({ content: reason.data.message });
            break;
        }
      }
    }
  },
};
</script>

核心集成要点:

  1. fastGo — 一行代码完成 URL 参数解析 → 登录 → SignalR 建连
  2. interceptors — 对请求参数和响应数据进行加工(至少做透传)
  3. loginRet — 获取业务系统的用户名、Token 等信息写入状态管理
  4. FastGoError — 通过 instanceof + code 实现类型安全的错误分支处理

标准接入(高级用法)

当需要完全手动控制 Token 获取流程或连接生命周期时,使用 EduSignalrSdk.create()

import { EduSignalrSdk } from "edu-signalr-sdk";

const initSdk = async () => {
  // 1. 获取实训临时 JWT
  const res = await fetch("/api/train/get-jwt", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ /* 自定义参数 */ }),
  });
  const data = await res.json();

  // 2. 创建 SDK 实例(自动完成 FingerprintJS 指纹采集 + 连接建立)
  const sdk = await EduSignalrSdk.create(
    {
      hubUrl: "https://你的教辅端域名/studyHub",
      accessToken: data.accessToken,
      expires: data.timestamp,
      refreshExpires: data.refreshTimestamp,
      // 可选:提供 token 刷新函数,重连时自动获取新 token
      tokenRefreshFn: async () => {
        const res = await fetch("/api/train/refresh-jwt", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ refreshToken: data.refreshToken }),
        });
        const d = await res.json();
        return d.sRToken || d.token || d.accessToken;
      },
      // 可选配置
      heartbeatInterval: 30000,  // 心跳间隔 30s
      idleTimeout: 120000,       // 无操作 2 分钟视为挂机
      maxReconnectTimes: 3,      // 最多重连 3 次
    },
    {
      onConnected: () => console.log("SignalR 连接成功,开始实训计时"),
      onDisconnect: () => console.log("连接已断开"),
      onReconnecting: () => console.log("正在重连..."),
      onReconnected: () => console.log("重连成功"),
      onKick: (msg) => {
        alert(msg);
        window.location.href = "/login";
      },
      onReconnectFail: () => {
        alert("与教辅服务连接异常,请关闭页面重新从教辅端进入");
      },
    }
  );

  // 查询连接状态
  console.log("当前状态:", sdk.state); // "Connected"

  // 页面关闭时销毁(sendBeacon 保证下线通知送达)
  window.addEventListener("beforeunload", () => {
    sdk.destroy();
  });
};

initSdk();

API 参考

fastGo(options)

一键启动方法,返回 Promise<{ sdk: EduSignalrSdk | null, loginRet: LoginRet }>

自动完成:

  1. URL 参数解析
  2. 通过 interceptors.request 加工请求参数
  3. 调用 autoLoginApi 获取登录信息及 SignalR Token
  4. 通过 interceptors.response 加工响应数据
  5. 创建 SignalR 连接(若存在 SignalR Token)
  6. 注册事件回调与自定义 Hub 方法
  7. 自动绑定 beforeunload 销毁

FastGoOptions

| 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | hubUrl | string | ✅ | - | 教辅端 SignalR Hub 地址 | | autoLoginApi | string | ✅ | - | 自动登录接口地址 | | srRefreshTokenApi | string | ✅ | - | SignalR Token 刷新接口地址 | | interceptors | { request, response } | ✅ | - | 请求/响应拦截器(见下方说明) | | requireSrFn | (params) => boolean | ❌ | - | 判断是否需要初始化 SignalR,返回 false 时仅登录不建连 | | heartbeatInterval | number | ❌ | 30000 | 心跳间隔(毫秒) | | idleTimeout | number | ❌ | 120000 | 挂机判定阈值(毫秒) | | maxReconnectTimes | number | ❌ | 3 | 最大重连次数 | | onConnected | () => void | ❌ | - | 首次连接成功回调 | | onDisconnect | () => void | ❌ | - | 连接断开回调 | | onReconnecting | () => void | ❌ | - | 正在重连回调 | | onReconnected | () => void | ❌ | - | 重连成功回调 | | onKick | (msg: string) => void | ❌ | - | 被踢下线回调 | | onReconnectFail | () => void | ❌ | - | 重连次数耗尽回调 | | hubMethods | Record<string, (...args: any[]) => void> | ❌ | - | 自定义 Hub 方法监听注册 | | autoDestroy | boolean | ❌ | true | 是否自动绑定 beforeunload 销毁 |

interceptors 说明

| 拦截器 | 签名 | 说明 | |--------|------|------| | request | (params: Record<string, any>) => Record<string, any> | 在调用登录接口前,对自动从 URL 解析的参数进行加工 | | response | (res: any) => any | 对登录接口返回的原始响应进行加工后交给 SDK |

返回值

{
  sdk: EduSignalrSdk | null,  // SignalR 实例(未建连时为 null)
  loginRet: LoginRet           // 登录返回的用户信息与 Token
}

LoginRet

| 字段 | 类型 | 说明 | |------|------|------| | userName | string | 用户名 | | accessToken | string | 业务系统访问令牌 | | refreshToken | string | 业务系统刷新令牌 | | tokenExpires | number | 业务访问令牌过期时间(秒级 Unix 时间戳) | | refreshTokenExpires | number | 业务刷新令牌过期时间(秒级 Unix 时间戳) | | [key: string] | any | 业务系统可返回更多自定义字段 |

FastGoError

fastGo 抛出异常时,可通过 instanceof 判断错误类型:

import { FastGoError } from "edu-signalr-sdk";

try {
  await fastGo({...});
} catch (reason) {
  if (reason instanceof FastGoError) {
    console.log(reason.code);  // "LOGIN_FAILED" | "UNKNOWN_ERROR"
    console.log(reason.data);  // 附加错误数据
  }
}

| 错误码 | 说明 | |--------|------| | LOGIN_FAILED | 登录接口返回 ok:falsedata 为服务端返回数据 | | UNKNOWN_ERROR | 网络异常或 SDK 创建失败,data 为原始异常对象 |


EduSignalrSdk.create(config, events)

异步工厂方法,返回 Promise<EduSignalrSdk>。自动完成指纹采集、SignalR 连接建立。

config: EduSignalrConfig

| 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | hubUrl | string | ✅ | - | 教辅端 SignalR Hub 地址 | | accessToken | string | ✅ | - | SignalR 通信用 JWT | | expires | number | ✅ | - | Token 过期时间(秒级 Unix 时间戳) | | refreshExpires | number | ✅ | - | 刷新 Token 过期时间(秒级 Unix 时间戳) | | tokenRefreshFn | () => Promise<string> | ❌ | - | Token 刷新函数,重连时自动调用 | | heartbeatInterval | number | ❌ | 30000 | 心跳间隔(毫秒) | | idleTimeout | number | ❌ | 120000 | 挂机判定阈值(毫秒) | | maxReconnectTimes | number | ❌ | 3 | 最大重连次数 |

events: EduSdkEvents

| 回调 | 参数 | 说明 | |------|------|------| | onConnected | 无 | 首次连接成功 | | onDisconnect | 无 | 连接断开 | | onReconnecting | 无 | 正在重连 | | onReconnected | 无 | 重连成功 | | onKick | msg: string | 被服务端踢下线 | | onReconnectFail | 无 | 重连次数耗尽 |


实例属性与方法

| 属性/方法 | 说明 | |-----------|------| | sdk.state | 只读属性,返回 SdkConnectionState 枚举值 | | sdk.destroy() | 销毁实例,发送下线通知并关闭连接 | | sdk.invoke(methodName, ...args) | 调用服务端 Hub 方法(兼容后期扩展) | | sdk.on(methodName, handler) | 注册服务端推送事件监听(兼容后期扩展) | | sdk.off(methodName, handler) | 移除服务端推送事件监听 |

sdk.invoke(methodName, ...args) 示例

// 调用服务端自定义 Hub 方法,无需升级 SDK
await sdk.invoke("SubmitAnswer", { questionId: 1, answer: "A" });
await sdk.invoke("RequestHint", questionId);

sdk.on(methodName, handler) 示例

// 在 fastGo 初始化后动态注册新事件
sdk.on("OnScoreUpdate", (data) => {
  console.log("成绩更新:", data);
});
sdk.on("OnNewMessage", (msg) => {
  console.log("新消息:", msg);
});

辅助工具函数

import {
  getDeviceFingerprint,
  isUserActive,
  getIdleSeconds,
  isPageVisible,
  detectAutomation,
  getBrowserInfo,
} from "edu-signalr-sdk";

// 获取浏览器设备指纹(基于 FingerprintJS 专业指纹库)
const fp = await getDeviceFingerprint();

// 检测自动化工具特征
const autoInfo = detectAutomation();
if (autoInfo.isAutomated) {
  console.warn("检测到自动化特征:", autoInfo.flags);
}

// 查看当前空闲秒数
console.log("已空闲:", getIdleSeconds(), "秒");

实训平台对接要求

自动登录接口 (autoLoginApi)

SDK 通过 POST 请求该接口,Body 为经过 interceptors.request 加工后的 URL 查询参数对象。

Response:

{
  "ok": true,
  "data": {
    "userName": "张三",
    "accessToken": "eyJhbG...(业务系统访问令牌)",
    "refreshToken": "eyJhbG...(业务系统刷新令牌)",
    "tokenExpires": 1700000000,
    "refreshTokenExpires": 1700604800,
    "signalr": {
      "accessToken": "eyJhbG...(SignalR 通信用 Token)",
      "refreshToken": "eyJhbG...(SignalR 刷新 Token)",
      "timestamp": 1700000000,
      "refreshTimestamp": 1700604800
    }
  }
}

说明

  • signalr 字段为可选。如果服务端不返回 signalrsignalr.accessToken 为空,fastGo 将仅执行登录流程,不建立 SignalR 连接(此时 ret.sdknullret.loginRet 仍可用)。
  • data 中的额外字段(除 signalr 外)均会出现在 loginRet 中。

SignalR Token 刷新接口 (srRefreshTokenApi)

SDK 在重连时通过 POST 请求该接口以获取新 Token,Body 包含 refreshToken 字段。

Response(兼容多种字段名):

{
  "sRToken": "eyJhbG...(新 Token)"
}

{
  "token": "eyJhbG..."
}

{
  "accessToken": "eyJhbG..."
}

心跳数据协议

SDK 每 30 秒向 Hub 发送 SendHeartbeat

{
  "msgType": "heartbeat",
  "timestamp": 1687000000000,
  "duration": 3600,
  "isActive": true,
  "finger": "a1b2c3d4..."
}
  • duration:累计实训时长(秒),从首次连接开始计算
  • isActive:当前是否活跃(有真实操作且页面可见)
  • finger:浏览器设备指纹 ID(由 FingerprintJS 生成,稳定度极高)

教辅端 Hub 接口规范

实训平台需要实现以下 SignalR Hub 方法:

| 方向 | 方法名 | 说明 | |------|--------|------| | SDK → Hub | SendHeartbeat | 心跳上报 | | SDK → Hub | SendLogout | 主动下线通知 | | Hub → SDK | KickNotify | 服务端踢人通知 |

自定义扩展

服务端可随时新增 Hub 方法,SDK 无需升级即可支持:

// 服务端新增客户端推送事件 → 前端注册监听
sdk.on("OnNewServerEvent", (payload) => { /* 处理 */ });

// 服务端新增可调用方法 → 前端调用
await sdk.invoke("NewServerMethod", { key: "value" });

集成检查清单

  • [ ] 安装 SDK — npm install edu-signalr-sdk
  • [ ] 配置 hubUrl — 指向教辅端 SignalR Hub 地址
  • [ ] 实现 autoLoginApi 接口 — 返回 { ok, data: { userName, accessToken, ..., signalr } }
  • [ ] 实现 srRefreshTokenApi 接口 — 接收 refreshToken,返回新 Token
  • [ ] 配置 interceptors — 至少做透传 { request: p => p, response: r => r }
  • [ ] 处理 loginRet — 将 Token 和用户信息写入状态管理
  • [ ] 处理 FastGoError — 捕获 LOGIN_FAILEDUNKNOWN_ERROR 并提示用户
  • [ ] 注册必要的事件回调 — 至少 onKick(被踢下线跳转登录页)
  • [ ] (可选)配置 requireSrFn — 当部分页面不需要长连接时跳过建连
  • [ ] (可选)配置 hubMethods — 注册服务端自定义推送事件

License

MIT