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

@dsz-examaware/plugin-sdk

v1.5.2

Published

ExamAware plugin SDK

Readme

ExamAware Plugin SDK

API V2 是 ExamAware 插件的标准开发接口。main 与 renderer 使用同一套领域命名、权限模型、生命周期和强类型 token;固定桌面能力通过 ctx.api 使用,跨进程动态服务通过 @dsz-examaware/rpc 使用。

快速开始

pnpm dlx create-examaware-plugin examaware-plugin-demo
cd examaware-plugin-demo
pnpm install
pnpm build
pnpm pack

插件 manifest 必须显式声明 V2、入口、权限和 renderer 激活窗口:

{
  "examaware": {
    "apiVersion": 2,
    "engines": {
      "desktop": ">=1.5.0",
      "sdk": ">=1.0.0 <2.0.0"
    },
    "targets": {
      "main": "./dist/main/index.cjs",
      "renderer": "./dist/renderer/index.mjs"
    },
    "permissions": ["player.start", "files.read", "network.http", "ui.contribute"],
    "activation": {
      "rendererWindows": ["main", "plugin"]
    }
  }
}

V2 manifest 采用严格校验。未知权限、未知窗口类型、不支持的 API 版本和越出插件目录的入口路径都会在加载时直接报错。

版本兼容采用范围而不是与客户端版本一一对应:

  • 客户端声明其接受的插件 SDK 范围;当前 1.x 客户端接受 >=1.0.0 <2.0.0
  • 插件可用 examaware.engines.desktop 声明可运行的客户端版本范围,用 examaware.engines.sdk 声明可接受的宿主 SDK 范围。
  • dependencies["@dsz-examaware/plugin-sdk"] 表示插件构建时使用的 SDK。旧插件写死的 1.4.4 会被视为最低 SDK 版本,因此可在提供 1.5.x SDK 的客户端中运行,而不是要求版本完全相等。
  • 为兼容历史清单,examaware.engines.sdk 中的裸版本也按“该版本至下一个主版本”解释;需要严格锁定时应显式写 =1.4.4

生命周期

import { defineMainPlugin, defineRendererPlugin } from '@dsz-examaware/plugin-sdk';

export default defineMainPlugin({
  onLoad(ctx) {},
  activate(ctx) {
    ctx.scope.defer(() => stopBackgroundTask());
    return () => releaseActivationResource();
  },
  onReady(ctx) {},
  deactivate(ctx) {},
  onUnload(ctx) {},
  onError(error, ctx) {
    ctx.logger.error(error.message, error.details);
  }
});

export const rendererEntry = defineRendererPlugin({
  activate(ctx) {
    if (ctx.window.kind !== 'main') return;
    ctx.api.ui.home.register({
      id: 'open',
      label: '打开插件',
      icon: 'extension',
      action: () => ctx.api.windows.open({ id: 'workspace', route: 'workspace' })
    });
  }
});

激活顺序是 onLoad -> activate -> onReady。停用顺序是 deactivate -> activate 返回的 disposable -> ctx.scope 反向释放 -> onUnload。某个清理阶段失败不会跳过后续阶段,最终错误统一传给 onError

注册 API 返回的资源会自动归属当前插件 scope。自行创建的计时器、订阅或第三方资源应使用 ctx.scope.defer()ctx.scope.add() 管理。

API 目录

main 与 renderer 共有以下领域:

| API | 用途 | | --------------------------- | --------------------------------------------------- | | app | 应用信息、自启动和退出 | | player | 配置预检、file/config/JSON/URL 放映、会话控制和事件 | | exams | 解析、详细校验、规范化和序列化考试配置 | | files / dialogs | 文件选择、读写、状态和监听 | | settings | 当前插件的类型化配置及变更订阅 | | windows | 插件窗口和标准桌面窗口 | | commands / events | 强类型进程内命令与事件 | | services | 同进程类型化服务和跨进程 RPC | | time / cast / network | 校时、投送和受 SSRF 防护的网络请求 | | http / deepLinks | HTTP 控制与 deep link | | logging / plugins | 日志和插件管理 |

renderer 额外提供 ui.home/pages/settings/editor/player/menus 贡献点。main 额外提供 http.registerRoute()deepLinks.register()

播放 API

所有入口最终进入同一条准备、详细校验、窗口 ACK 和会话状态链路:

const validation = ctx.api.exams.validate(candidate, {
  overlap: 'warning',
  allowEmptyExamInfos: false
});

if (!validation.valid) {
  for (const issue of validation.errors) {
    ctx.logger.error(`${issue.path}: ${issue.message}`);
  }
  return;
}

const fromFile = await ctx.api.player.startFromFile('/path/to/exam.ea2');
const fromConfig = await ctx.api.player.startFromConfig(validation.config!);
const fromJson = await ctx.api.player.startFromJson(json);
const fromUrl = await ctx.api.player.startFromUrl('https://example.com/exam.ea2');

await fromUrl.focus();
const replacement = await fromUrl.replaceSource({ kind: 'config', config: nextConfig });
await replacement.close();

file 源需要 files.read,URL 源需要 network.http,访问本机或私网还需要 network.local。URL 请求限制协议、重定向、响应大小和超时,并对每次 DNS/重定向目标重新做私网检查。

强类型协议

在 shared contracts 文件中只定义一次命名空间和 token。其余代码导入 token,不传裸服务名或方法名:

import { createPluginTokens } from '@dsz-examaware/plugin-sdk';

const tokens = createPluginTokens('examaware-plugin-demo');

export const refreshCommand = tokens.command<{ force: boolean }, number>('refresh');
export const configChanged = tokens.event<{ revision: number }>('config-changed');
export const localCache = tokens.service<{ revision: number }>('local-cache');

export interface BackService {
  load(id: string): Promise<{ value: string }>;
}
export const backService = tokens.rpc<BackService>('back-service');
// main
ctx.api.services.exposeRpc(backService, {
  async load(id) {
    return { value: id };
  }
});

// renderer,返回值自动 Promise 化,方法名和参数由 IDE 校验
const back = ctx.api.services.rpc(backService);
const result = await back.load('item-1');

services.provide/use 只用于同一进程内的对象服务。跨 main/renderer 边界必须使用 services.exposeRpc/rpc,函数对象不会通过 Electron structured clone 偷渡。

权限

权限在调用点执行,并由 API 模块声明自动预检。主要权限组包括:

  • player.*, files.*, windows.*, app.*, ui.*
  • network.*, http.*, cast.*, time.*, deep-links.*
  • logging.*, plugins.*, services.*, commands.*, events.*

通过 PluginPermissions 常量引用权限;不要在 TypeScript 业务代码中重复权限字符串。ctx.permissions.has() 可用于可选能力分支,ctx.permissions.require() 用于显式前置检查。

API 模块注册

桌面宿主使用 definePluginApiModule() 定义领域模块,并在创建插件 context 时通过显式模块目录自动装配。目录负责依赖排序、进程范围、权限预检、循环检测、字段冲突检测和反向释放。

这里刻意不使用 import-time decorator 或文件系统 glob 自动注册:显式目录可以静态审查、稳定打包和测试,同时仍然避免每个模块手写不同的初始化协议。

HostBuilderdefineExamAwarePlugin 和字符串 RPC helper 仅为 API V1 兼容保留。新插件应使用 V2 生命周期、ctx.api、类型化 token 和 manifest 权限。