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

@northsea4/tab-proxy-service

v0.2.1

Published

A type-safe wrapper for calling services registered in content scripts from background or other tabs, with automatic type inference

Readme

@northsea4/tab-proxy-service

一个类型安全的 WebExtension 工具库,支持在 content script 中注册 service,并允许从 background 或其他 tab 调用这些 service。

特性

在 Content Script 中注册 Service - 与 @webext-core/proxy-service 相反,支持在 tab 中注册服务
Background 调用 Tab Service - 从 background script 调用任意 tab 中的服务
Tab 间通信 - 支持一个 tab 调用另一个 tab 中的服务(通过 background 转发)
完整的类型安全 - 使用 TypeScript 泛型确保类型安全
自动类型推断 - 使用 defineTabProxyService 自动获得类型提示
嵌套对象支持 - 支持深层嵌套的对象和方法调用
自动清理 - Tab 关闭时自动注销服务
查询功能 - 可查询所有已注册的 tab services

安装

# 使用 pnpm
pnpm add @northsea4/tab-proxy-service

# 使用 npm
npm install @northsea4/tab-proxy-service

# 使用 yarn
yarn add @northsea4/tab-proxy-service

快速开始

💡 推荐: 使用 defineTabProxyService API,它会自动提供完整的类型提示,无需手动传递泛型类型。

步骤 1: 定义服务

支持三种服务定义方式:

方式 A: 使用 Class (推荐)

// services/hello-service.ts
import { defineTabProxyService } from '@northsea4/tab-proxy-service'

// 使用 class 定义服务
class HelloService {
  async sayHello(name: string): Promise<string> {
    return `Hello, ${name}! From tab: ${document.title}`
  }

  async getTabInfo(): Promise<{ title: string; url: string }> {
    return {
      title: document.title,
      url: window.location.href
    }
  }
}

// 定义服务 - 自动获得类型提示! 🎉
export const [registerHelloService, getHelloService] = defineTabProxyService(
  'hello-service',
  () => new HelloService()
)

方式 B: 使用对象字面量

export const [registerPageService, getPageService] = defineTabProxyService('page-service', () => ({
  async getTitle() {
    return document.title
  },
  async getUrl() {
    return window.location.href
  }
}))

方式 C: 带参数的服务

class ConfigService {
  constructor(private apiUrl: string) {}

  async fetchConfig() {
    return fetch(`${this.apiUrl}/config`).then((r) => r.json())
  }
}

// 支持初始化参数
export const [registerConfigService, getConfigService] = defineTabProxyService(
  'config-service',
  (apiUrl: string) => new ConfigService(apiUrl)
)

步骤 2: 在 Content Script 中注册服务

// content-script.ts
import { registerHelloService } from './services/hello-service'

// 注册服务
const cleanup = await registerHelloService()

// 页面卸载时清理
window.addEventListener('beforeunload', cleanup)

// 如果服务需要参数
// const cleanup = await registerConfigService('https://api.example.com');

步骤 3: 在 Background 中调用服务

// background.ts
import { getHelloService } from './services/hello-service'

// 获取服务 - 自动有完整的类型提示和自动补全! 🚀
const helloService = getHelloService({ targetTabId: 123 })

// sayHello 方法会有自动补全和类型检查 ✨
const greeting = await helloService.sayHello('World')
const info = await helloService.getTabInfo()

传统方式 (不推荐)

⚠️ 注意: 以下方式需要手动传递类型参数,建议使用上面的 defineTabProxyService 方式。

使用 registerTabServicecreateTabProxyService (需要手动传递类型参数):

1. 在 Background 中初始化管理器

首先,必须在 background script 中初始化 tab service 管理器:

// background.ts
import { initTabServiceManager } from '@northsea4/tab-proxy-service'

// 启动时初始化
initTabServiceManager({
  logger: console // 可选:启用日志
})

2. 在 Content Script 中注册 Service

// content-script.ts
import { registerTabService } from '@northsea4/tab-proxy-service'

// 定义你的 service
const pageService = {
  async getPageTitle(): Promise<string> {
    return document.title
  },

  async getPageUrl(): Promise<string> {
    return window.location.href
  },

  async extractData(): Promise<any[]> {
    // 提取页面数据
    return Array.from(document.querySelectorAll('.item')).map((el) => ({
      text: el.textContent,
      href: el.getAttribute('href')
    }))
  }
}

// 注册 service
const cleanup = await registerTabService('page-service', pageService)

// 页面卸载时清理
window.addEventListener('beforeunload', cleanup)

3. 从 Background 调用 Tab Service

// background.ts
import { createTabProxyService, type TabProxyServiceKey } from '@northsea4/tab-proxy-service'

// 定义类型
interface PageService {
  getPageTitle(): Promise<string>
  getPageUrl(): Promise<string>
  extractData(): Promise<any[]>
}

// 创建代理(会调用任意一个注册了该 service 的 tab)
const pageService = createTabProxyService<PageService>('page-service')

// 使用
const title = await pageService.getPageTitle()
const data = await pageService.extractData()

console.log('Page title:', title)
console.log('Extracted data:', data)

4. 调用特定 Tab 中的 Service

// background.ts 或 另一个 content-script.ts
import { createTabProxyService } from '@northsea4/tab-proxy-service'

// 指定目标 tab ID
const specificTabService = createTabProxyService<PageService>('page-service', {
  targetTabId: 123 // 目标 tab 的 ID
})

const title = await specificTabService.getPageTitle()

5. Tab 间通信示例

// content-script-a.ts (Tab A)
import { registerTabService } from '@northsea4/tab-proxy-service'

const dataService = {
  async shareData() {
    return { data: 'from tab A', timestamp: Date.now() }
  }
}

await registerTabService('data-service', dataService)
// content-script-b.ts (Tab B)
import { createTabProxyService } from '@northsea4/tab-proxy-service'

// Tab B 调用 Tab A 中的服务
const dataService = createTabProxyService('data-service', {
  targetTabId: tabAId // Tab A 的 ID
})

const data = await dataService.shareData()
console.log('Data from Tab A:', data)

类型安全最佳实践

方式 1: 使用 defineTabProxyService (推荐)

这是最简单的方式,自动获得类型提示:

// services/page-service.ts
import { defineTabProxyService } from '@northsea4/tab-proxy-service'

class PageService {
  async getTitle(): Promise<string> {
    return document.title
  }

  async extractLinks(): Promise<string[]> {
    return Array.from(document.querySelectorAll('a'))
      .map((a) => a.href)
      .filter(Boolean)
  }
}

// 导出注册和获取函数
export const [registerPageService, getPageService] = defineTabProxyService(
  'page-service',
  () => new PageService()
)

使用:

// content-script.ts
import { registerPageService } from './services/page-service'
await registerPageService()

// background.ts
import { getPageService } from './services/page-service'
const pageService = getPageService({ targetTabId: 123 })
const title = await pageService.getTitle() // ✅ 自动类型提示

方式 2: 使用 TabProxyServiceKey

如果使用传统方式,推荐使用类型约束的 service key:

// service-keys.ts
import type { TabProxyServiceKey } from '@northsea4/tab-proxy-service'
import type { PageService } from './page-service'

// 使用类型约束的 key
export const PAGE_SERVICE_KEY = 'page-service' as TabProxyServiceKey<PageService>
// content-script.ts
import { registerTabService } from '@northsea4/tab-proxy-service'
import { PAGE_SERVICE_KEY } from './service-keys'
import { createPageService } from './page-service'

const pageService = createPageService()
await registerTabService(PAGE_SERVICE_KEY, pageService)
// background.ts
import { createTabProxyService } from '@northsea4/tab-proxy-service'
import { PAGE_SERVICE_KEY } from './service-keys'

// 自动推断类型,无需手动指定泛型
const pageService = createTabProxyService(PAGE_SERVICE_KEY)

查询已注册的 Services

import { queryTabServices } from '@northsea4/tab-proxy-service'

const services = await queryTabServices()
console.log('Registered services:', services)
// [
//   { tabId: 123, serviceKey: 'page-service', registeredAt: 1234567890 },
//   { tabId: 456, serviceKey: 'data-service', registeredAt: 1234567891 },
// ]

支持的 Service 类型

1. Class(推荐)

使用 class 定义服务提供了最好的代码组织和可维护性:

// services/data-service.ts
import { defineTabProxyService } from '@northsea4/tab-proxy-service'

class DataService {
  private cache = new Map<string, any>()

  constructor(private apiUrl?: string) {}

  async getData(key: string): Promise<any> {
    if (this.cache.has(key)) {
      return this.cache.get(key)
    }

    const data = await fetch(`${this.apiUrl || ''}/data/${key}`).then((r) => r.json())
    this.cache.set(key, data)
    return data
  }

  async setData(key: string, value: any): Promise<void> {
    this.cache.set(key, value)
  }

  async clearCache(): Promise<void> {
    this.cache.clear()
  }
}

// 定义服务
export const [registerDataService, getDataService] = defineTabProxyService(
  'data-service',
  (apiUrl?: string) => new DataService(apiUrl)
)

优点:

  • ✅ 支持私有成员和状态管理
  • ✅ 支持构造函数参数
  • ✅ 良好的代码组织
  • ✅ 完整的 IDE 支持

使用:

// content-script.ts
const cleanup = await registerDataService('https://api.example.com')

// background.ts
const dataService = getDataService({ targetTabId: 123 })
const data = await dataService.getData('users')

2. 对象字面量

简单场景可以使用对象字面量:

export const [registerMyService, getMyService] = defineTabProxyService('my-service', () => ({
  async getData(): Promise<Data> {
    // ...
  },
  async setData(data: Data): Promise<void> {
    // ...
  }
}))

3. 函数

async function myFunction(arg: string): Promise<number> {
  // ...
}

export const [registerMyFunction, getMyFunction] = defineTabProxyService(
  'my-function',
  () => myFunction
)

4. 嵌套对象

### 4. 嵌套对象

支持深层嵌套的对象和方法调用:

```ts
export const [registerApiService, getApiService] = defineTabProxyService(
  'api-service',
  () => ({
    users: {
      async list(): Promise<User[]> { /* ... */ },
      async get(id: string): Promise<User> { /* ... */ },
    },
    posts: {
      async list(): Promise<Post[]> { /* ... */ },
      async create(post: Post): Promise<void> { /* ... */ },
    },
  })
);

使用:

const api = getApiService({ targetTabId: 123 })
const users = await api.users.list()
const posts = await api.posts.list()

辅助工具

flattenPromise

用于简化处理 Promise<Dependency>:

import { flattenPromise } from '@northsea4/tab-proxy-service'

function createService(dbPromise: Promise<Database>) {
  const db = flattenPromise(dbPromise)

  return {
    async getData() {
      // 不需要 await (await dbPromise).query()
      return await db.query('SELECT * FROM table')
    }
  }
}

与 @webext-core/proxy-service 的对比

| 功能 | @webext-core/proxy-service | @northsea4/tab-proxy-service | | -------------------------- | -------------------------- | ------------------------------- | | 在 Background 注册服务 | ✅ | ❌ | | 在 Content Script 注册服务 | ❌ | ✅ | | Background → Content 调用 | ❌ | ✅ | | Content → Background 调用 | ✅ | 使用 @webext-core/proxy-service | | Tab → Tab 调用 | ❌ | ✅ | | 类型安全 | ✅ | ✅ | | 嵌套对象支持 | ✅ | ✅ |

建议: 两个库可以配合使用:

  • 使用 @webext-core/proxy-service 在 background 注册服务,供其他上下文调用
  • 使用 @northsea4/tab-proxy-service 在 content script 注册服务,供 background 或其他 tab 调用

完整示例

// ===== services/page-service.ts =====
import { defineTabProxyService } from '@northsea4/tab-proxy-service'

class PageService {
  async getTitle(): Promise<string> {
    return document.title
  }

  async extractLinks(): Promise<string[]> {
    return Array.from(document.querySelectorAll('a'))
      .map((a) => a.href)
      .filter(Boolean)
  }
}

export const [registerPageService, getPageService] = defineTabProxyService(
  'page-service',
  () => new PageService()
)

// ===== content-script.ts =====
import { registerPageService } from './services/page-service'

// 注册服务
const cleanup = await registerPageService()

// 清理
window.addEventListener('beforeunload', cleanup)

// ===== background.ts =====
import { initTabServiceManager } from '@northsea4/tab-proxy-service'
import { getPageService } from './services/page-service'

// 初始化管理器
initTabServiceManager({ logger: console })

// 监听扩展图标点击
browser.action.onClicked.addListener(async (tab) => {
  if (!tab.id) return

  // 调用当前 tab 中的服务
  const pageService = getPageService({ targetTabId: tab.id })

  try {
    const title = await pageService.getTitle()
    const links = await pageService.extractLinks()

    console.log('Page title:', title)
    console.log('Links found:', links.length)
  } catch (error) {
    console.error('Failed to call service:', error)
  }
})

API 参考

defineTabProxyService(name, init, config?) (推荐)

定义一个 tab proxy service 的辅助函数,自动提供类型提示。

参数:

  • name: string - Service 的唯一名称
  • init: (...args: TArgs) => TService - 初始化函数,返回服务实例
  • config?: TabProxyServiceConfig - 可选配置

返回: [registerService, getService] - 返回元组

  • registerService: (...args: TArgs) => Promise<() => void> - 注册服务函数
  • getService: (options?) => TabProxyService<TService> - 获取服务代理函数

示例:

export const [registerHelloService, getHelloService] = defineTabProxyService(
  'hello-service',
  () => new HelloService()
)

initTabServiceManager(config?)

在 background 中初始化 tab service 管理器。必须在使用其他功能前调用。

参数:

  • config?: TabProxyServiceConfig - 可选配置
    • logger?: Console - 日志记录器
    • timeout?: number - 消息超时时间(毫秒)

registerTabService(key, service, config?)

在 content script 中注册一个服务。

参数:

  • key: string | TabProxyServiceKey<T> - Service key
  • service: T - 服务实例
  • config?: TabProxyServiceConfig - 可选配置

返回: Promise<() => void> - 返回清理函数

createTabProxyService(key, options?)

创建一个 tab service 的代理。

参数:

  • key: string | TabProxyServiceKey<T> - Service key
  • options?:
    • targetTabId?: number - 目标 tab ID(可选)
    • config?: TabProxyServiceConfig - 配置(可选)

返回: TabProxyService<T> - Service 代理

queryTabServices()

查询所有已注册的 tab services。

返回: Promise<TabServiceInfo[]> - Service 信息列表

flattenPromise(promise)

扁平化 Promise,用于简化处理 Promise<Dependency>

参数:

  • promise: Promise<T> - 要扁平化的 Promise

返回: TabProxyService<T> - 扁平化后的代理

License

MIT

作者

nornorlunn

致谢

本项目受 @webext-core/proxy-service 启发,实现了相反方向的代理服务功能。