@powertoys/relay
TypeScript icon, indicating that this package has built-in type declarations

1.3.0 • Public • Published

@PowerToys/Relay

A lightweight, TypeScript-first library for communication between browser tabs. This library enables seamless interaction between multiple open tabs/windows of your application using SharedWorker.

Key Features

  • Type-Safe: Full TypeScript support with extensive type definitions
  • Framework Agnostic: Core functionality works with any JavaScript framework
  • React Integration: Built-in React hooks and context for easy integration
  • Action-Based Communication: Define and execute actions across tabs
  • Observable Pattern: Custom observable implementation (no RxJS dependency)
  • Heartbeat System: Automatic detection of closed or disconnected tabs
  • Minimal Dependencies: Zero external runtime dependencies
  • Extensible: Easy to add custom action handlers and extend functionality

Installation

pnpm add @powertoys/relay

npm install @powertoys/relay

yarn add @powertoys/relay

Basic Usage

1. Define Your Action Handlers

// handlers/worksheet-handlers.ts
import { ActionHandler } from "@powertoys/relay";

export interface SelectRangePayload {
  address: string;
  sheetName?: string;
}

const selectRange: ActionHandler<SelectRangePayload, void> = async payload => {
  const { address, sheetName } = payload;
  // Implementation...
};

export const worksheetHandlers = {
  SELECT_RANGE: selectRange,
};

// handlers/workbook-handlers.ts
export interface WorkbookInfo {
  name: string;
  sheets: string[];
}

const getWorkbookInfo: ActionHandler<void, WorkbookInfo> = async () => {
  // Implementation...
  return {
    name: "Example Workbook",
    sheets: ["Sheet1", "Sheet2"],
  };
};

export const workbookHandlers = {
  GET_WORKBOOK_INFO: getWorkbookInfo,
};

2. Initialize the Relay

// Function to generate tab info (optional)
async function generateTabInfo() {
  const randomId = `tab_${Math.random().toString(36).slice(2, 9)}`;
  return {
    tabId: randomId,
    tabName: `Document ${randomId.slice(-4)}`,
  };
}

// Create the relay instance
export const relay = new Relay({
  handlers: {
    ...worksheetHandlers,
    ...workbookHandlers,
  },
  getTabInfo: generateTabInfo,
});

3. Use with React

// App.tsx

function App() {
  return (
    <RelayProvider relay={relay}>
      <Dashboard />
    </RelayProvider>
  );
}
// Dashboard.tsx
function Dashboard() {
  const tabInfo = useTabInfo();
  const tabList = useTabList();
  const requestAction = useRequestAction();

  const getWorkbookInfo = async tabId => {
    try {
      const info = await requestAction<WorkbookInfo>(tabId, "GET_WORKBOOK_INFO");
      console.log("Workbook info:", info);
    } catch (error) {
      console.error("Error:", error);
    }
  };

  return (
    <div>
      <h1>Current Tab: {tabInfo?.tabName}</h1>
      <h2>Connected Tabs:</h2>
      <ul>
        {tabList.map(tab => (
          <li key={tab.tabId}>
            {tab.tabName}
            {tab.tabId !== tabInfo?.tabId && <button onClick={() => getWorkbookInfo(tab.tabId)}>Get Info</button>}
          </li>
        ))}
      </ul>
    </div>
  );
}

Framework Agnostic Usage

import { Relay } from "@powertoys/relay";

const relay = new Relay({
  handlers: {
    PING: async () => "PONG",
  },
  getTabInfo: async () => ({ tabId: "unique-id", tabName: "My Document" }),
});

// Initialize
await relay.initialize();

// Subscribe to events
relay.onEvent({
  next: event => {
    if (event.type === "tabListUpdated") {
      console.log("Connected tabs:", event.tabs);
    }
  },
});

// Request an action from another tab
const targetTabId = "another-tab-id";
try {
  const result = await new Promise((resolve, reject) => {
    relay.requestAction(targetTabId, "SOME_ACTION", { data: "example" }, resolve, reject);
  });
  console.log("Result:", result);
} catch (error) {
  console.error("Error:", error);
}

// Clean up when done
relay.cleanup();

API Reference

Relay

The core class for managing tab-to-tab communication.

Constructor

constructor(options: RelayOptions)

Options:

  • handlers (Record<string, ActionHandler>): Map of action handlers
  • getTabInfo (() => Promise): Function to get tab info
  • heartbeatInterval (number, optional): Interval for sending heartbeats (default: 10000ms)

Methods

  • initialize(): Promise<void>: Initialize the communicator
  • getTabList(): void: Request the current tab list
  • requestAction<TResult>(targetTabId: string, action: string, payload?: any, onSuccess?: (result: TResult) => void, onError?: (error: string) => void): string | null: Request an action from another tab
  • cleanup(): void: Clean up the communicator
  • onEvent(subscriber: Subscriber<TabCommunicatorEvent>): () => void: Subscribe to all events
  • onTabListUpdated(subscriber: Subscriber<TabListItem[]>): () => void: Subscribe to tab list updates
  • getTabInfo(): TabInfo | null: Get the current tab info
  • isConnected(): boolean: Check if the communicator is initialized

React Hooks

  • useRelay(): Access the Relay instance
  • useTabInfo(): Get the current tab info
  • useTabList(): Get the list of connected tabs
  • useRequestAction(): Get a function to request actions from other tabs

Advanced Features

Custom Observables

The library includes a lightweight Observable implementation:

import { Observable } from "@powertoys/relay";

const counter$ = new Observable<number>();

// Subscribe
const unsubscribe = counter$.subscribe({
  next: value => console.log("Count:", value),
  error: err => console.error("Error:", err),
  complete: () => console.log("Completed"),
});

// Emit values
counter$.next(1);
counter$.next(2);

// Unsubscribe
unsubscribe();

Type-Safe Action Handlers

Define type-safe action handlers with proper payload and result types:

import { ActionHandler } from "@powertoys/relay";

interface UserPayload {
  userId: string;
}

interface UserProfile {
  id: string;
  name: string;
  email: string;
}

const getUserProfile: ActionHandler<UserPayload, UserProfile> = async payload => {
  const { userId } = payload;
  // Implementation...
  return {
    id: userId,
    name: "John Doe",
    email: "john@example.com",
  };
};

Browser Support

This library uses SharedWorker which is supported in:

  • Chrome 4+
  • Firefox 29+
  • Edge 79+
  • Safari 10.1+
  • Opera 10.6+

For browsers that don't support SharedWorker, you can implement a fallback using localStorage or BroadcastChannel.

License

MIT

Package Sidebar

Install

npm i @powertoys/relay

Weekly Downloads

3

Version

1.3.0

License

MIT

Unpacked Size

59.5 kB

Total Files

22

Last publish

Collaborators

  • boramdarkwill