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

webpage-tunnel

v1.3.0

Published

A secure and elegant cross-iframe API communication library based on `postMessage`.

Downloads

24

Readme

Webpage Tunnel

A secure and elegant cross-iframe API communication library based on postMessage.

webpage-tunnel enables seamless, type-safe API calls between parent pages and iframes, eliminating the complexity of postMessage protocol handling. With just a few lines of code, you can establish a communication tunnel between pages.

Use Cases

  • 🔗 Micro Frontends: Communication between micro-applications
  • 📦 Third-party Integration: Secure data exchange with embedded third-party pages
  • 🎨 Visual Editors: Communication between canvas and preview iframes

Features

  • Simple API: Intuitive serve() and Request API design
  • 🔒 Type Safe: Full TypeScript support with complete type inference
  • High Performance: Lightweight design (~2KB gzipped)
  • 🎯 Promise-based: All API calls return Promises
  • 🔧 Error Handling: Built-in timeout and error handling
  • 🌐 Cross-domain Support: Secure communication across different domains
  • 📦 Multiple Formats: Supports UMD, ESM, and CJS

Demo

https://webpage-tunnel.parksben.xyz

Table of Contents

Installation

NPM:

npm install webpage-tunnel
# or
yarn add webpage-tunnel
# or
pnpm add webpage-tunnel

CDN(UMD):

<script src="https://unpkg.com/webpage-tunnel/dist/webpage-tunnel.umd.js"></script>
<script>
  const { serve, Request } = window.WebpageTunnel;
</script>

CDN(ESM):

<script type="module">
import { serve, Request } from 'https://unpkg.com/webpage-tunnel/dist/webpage-tunnel.esm.js';
</script>

Quickstart

1. Embed Page

Use iframe to embed Page A into Page B:

<!-- Page B HTML -->
<iframe src="https://a.com/profile"></iframe>

Alternatively, you can use iframe to embed Page B into Page A:

<!-- Page A HTML -->
<iframe src="https://b.com/dashboard"></iframe>

You can also embed both Page A and Page B into the same host page:

<!-- Host Page HTML -->
<iframe src="https://a.com/profile"></iframe>
<iframe src="https://b.com/dashboard"></iframe>

The webpage-tunnel framework works correctly in all three scenarios.

2. Expose Page API

Use the serve() method in Page A to expose APIs (with types):

// Page A: profile page (https://a.com/profile)
import { serve } from 'webpage-tunnel';

interface ApiResponse<T> {
  status: number;
  message: string;
  data?: T;
}

interface RequestParams { userId: string }
interface UserInfo { id: string; name: string; email: string; avatar: string }
interface PlayListItem { id: string; title: string; cover: string }

// Expose API methods using serve()
const cleanup = serve({
  getUserInfo: async ({ userId }: RequestParams): Promise<ApiResponse<UserInfo>> => {
    const { data } = await fetch(`/api/user/${userId}/info`).then(res => res.json());
    return {
      status: 1,
      message: 'Success',
      data: {
        id: userId,
        name: data.name,
        email: data.email,
        avatar: data.avatar,
      },
    };
  },
  getPlayList: async ({ userId }: RequestParams): Promise<ApiResponse<PlayListItem[]>> => {
    const { data } = await fetch(`/api/user/${userId}/playList`).then(res => res.json());
    if (Array.isArray(data) && data.length > 0) {
      return { status: 1, message: 'Success', data };
    }
    return { status: 0, message: 'Play list is empty', data: [] };
  },
});

// Clean up when page unloads
window.addEventListener('beforeunload', () => {
  cleanup();
});

3. Call Page API

Use a Request instance in Page B to call the APIs exposed in Page A (with generics):

// Page B: dashboard page (https://b.com/dashboard)
import { Request } from 'webpage-tunnel';

// Reuse types from above: ApiResponse, RequestParams, UserInfo, PlayListItem

// Create Request instance
const userApi = new Request({
  server: 'https://a.com/profile',              // Target page URL
  methods: ['getUserInfo', 'getPlayList'],      // List of API methods to call
  timeout: 10 * 1000,                           // Optional: request timeout (ms)
});

// Define request parameters
const params: RequestParams = { userId: '123' };

// Call API methods
try {
  const userInfo = await userApi.getUserInfo<RequestParams, ApiResponse<UserInfo>>(params);
  console.log('User Info:', userInfo.data);
  
  const playList = await userApi.getPlayList<RequestParams, ApiResponse<PlayListItem[]>>(params);
  console.log('Play List:', playList.data);
} catch (error) {
  console.error('API call failed:', error.message);
}
  
// Destroy Request instance when page unloads
window.addEventListener('beforeunload', () => {
  userApi.destroy();
});

API Reference

serve(methods)

Expose API methods to allow other pages to call them.

Parameters:

| Parameter | Type | Required | Description | | --------- | ---------------------------- | -------- | ----------------------------- | | methods | Record<string, ApiHandler> | ✅ | Object containing API methods |

Return Value:

serve() returns a cleanup function that removes the message event listener:

const cleanup = serve(methods);

// Later, when you need to stop listening for messages
cleanup();  // Removes the message event listener

ApiHandler Type:

type ApiHandler<P = any, R = any> = (params: P) => R | Promise<R>

Example:

import { serve } from 'webpage-tunnel';

serve({
  // Synchronous method
  add(params: { a: number; b: number }) {
    return params.a + params.b;
  },
  
  // Asynchronous method
  async fetchData(params: { url: string }) {
    const response = await fetch(params.url);
    return response.json();
  },
  
  // Method with complex types
  async processUser(params: { user: User }) {
    // Process user data
    return { success: true, user: params.user };

  -**Return Value:**
  -
  -`serve()` returns a cleanup function that removes the message event listener:
  -
  -```typescript
  -const cleanup = serve(methods);
  -
  -// Later, when you need to stop listening for messages
  -cleanup();  // Removes the message event listener
  -```
  }
});

Notes:

  • serve() should only be called once per page
  • Methods can be synchronous or asynchronous
  • Methods receive a single params object parameter
  • Methods can return any JSON-serializable value
  • The cleanup function should be called when unloading the page or when no longer needed

Request

Create an API client to call remote page methods.

Constructor Parameters:

| Parameter | Type | Required | Default | Description | | ------------------- | ---------- | -------- | ------- | -------------------------------------------------------------------------------------------- | | options.server | string | ✅ | - | Target page URL (must include protocol and domain) | | options.methods | string[] | ✅ | - | List of API method names to call | | options.timeout | number | ❌ | 30000 | Request timeout in milliseconds | | options.connectionTimeout | number | ❌ | 5000 | Maximum time (in milliseconds) to wait for connection establishment | | options.targetWindow | Window | ❌ | - | Specific target window for multiple matching iframes. If omitted, broadcasts to all matching iframes |

Instance Methods:

Request instances dynamically add methods based on options.methods. Each method returns a Promise.

| Method | Description | | -------------------------------------------- | ----------------------------------------- | | [methodName]<P, R>(params?: P): Promise<R> | Call remote API with generic type support. Will automatically wait for connection if not yet established | | destroy(): void | Destroy instance and cleanup resources |

Example:

import { Request } from 'webpage-tunnel';

// Create Request instance (broadcast to all matching iframes)
const api = new Request({
  server: 'https://example.com/page',
  methods: ['getUser', 'updateUser', 'deleteUser'],
  timeout: 5000,
  connectionTimeout: 3000  // Wait up to 3s for connection
});

// Create Request instance targeting a specific iframe
const iframe = document.querySelector('iframe');
const apiSpecific = new Request({
  server: 'https://example.com/page',
  methods: ['getUser', 'updateUser', 'deleteUser'],
  targetWindow: iframe?.contentWindow || undefined
});

// Call method with types
interface User {
  id: string;
  name: string;
}

const user = await api.getUser<{ id: string }, User>({ id: '123' });
console.log(user.name);

// Call method without types
const result = await api.updateUser({ id: '123', name: 'John' });

// Destroy instance
api.destroy();

Type Definitions

ApiHandler<P, R>

API handler function type.

type ApiHandler<P = any, R = any> = (params: P) => R | Promise<R>

| Generic Parameter | Description | | ----------------- | -------------- | | P | Parameter type | | R | Return type |


RequestOptions

Request constructor configuration options.

interface RequestOptions {
  server: string;             // Target page URL
  methods: string[];          // List of API method names
  timeout?: number;           // Request timeout in milliseconds (default: 30000)
  connectionTimeout?: number; // Connection timeout in milliseconds (default: 5000)
  targetWindow?: Window;      // Specific target window (optional)
}

Message<T>

Internal communication message structure (for advanced users).

interface Message<T = any> {
  type: MessageType;   // Message type
  id: string;          // Unique message ID
  method?: string;     // API method name
  params?: T;          // Request parameters
  result?: any;        // Response result
  error?: string;      // Error message
}

Error Handling

All methods called through Request return Promises, so you can use try-catch or .catch() to handle errors:

Common Errors:

| Error Message | Description | Solution | | --------------------------- | -------------------------------------------------- | -------------------------------------------------------- | | Connection timeout after [time]ms | Connection establishment timeout | Increase connectionTimeout or ensure target page loads correctly | | Handshake timeout with [server] | Handshake with target page timeout | Check if target page calls serve() correctly | | No target window found for [server] | Target iframe not found or inaccessible | Ensure iframe is loaded and accessible | | Request timeout: [method] | API call timeout | Increase timeout or optimize server response | | Method [method] not found | Called method not registered on server | Confirm method is defined in serve() | | Request cancelled | Request cancelled (usually by calling destroy()) | Normal behavior, no action needed |

Example:

try {
  const data = await api.getData({ id: '123' });
  console.log(data);
} catch (error) {
  if (error.message.includes('timeout')) {
    console.error('Request timeout, please try again');
  } else if (error.message.includes('not found')) {
    console.error('API method not found');
  } else {
    console.error('Request failed:', error.message);
  }
}

Technical Overview

Based on the browser's native postMessage mechanism, webpage-tunnel establishes a secure communication channel between two pages. Through message passing, it implements method invocation and data exchange, ensuring data security and integrity in cross-domain environments.

Best Practices

1. Resource Cleanup with serve()

Always store and call the cleanup function returned by serve() when the page unloads

// Store the cleanup function
const cleanup = serve({
  getUserInfo: async (params) => {
    // Implementation
  },
  getPlayList: async (params) => {
    // Implementation
  }
});

// Call cleanup when page unloads or service is no longer needed
window.addEventListener('beforeunload', () => {
  cleanup();  // Removes the message event listener
});

// Or in a cleanup function (React/Vue)
onUnmounted(() => {
  cleanup();
});

Why this matters:

  • Prevents memory leaks by removing event listeners
  • Ensures proper resource management in single-page applications
  • Avoids potential issues when pages are reloaded or navigated away

2. Type Safety

Use TypeScript and define explicit types for API methods

interface GetUserParams { id: string }
interface UserResponse { id: string; name: string }

const user = await api.getUser<GetUserParams, UserResponse>({ id: '123' });

3. Error Handling

Always add error handling for API calls

serve({
  async getData(params) {
    try {
      return await fetchData(params);
    } catch (error) {
      return { error: error.message };
    }
  }
});

4. Resource Cleanup in Request

Call destroy() promptly when Request instances are no longer needed

// On component unmount
componentWillUnmount() {
  this.api.destroy();
}

5. Timeout Configuration

Set timeout appropriately based on network conditions

const api = new Request({
  server: 'https://example.com',
  methods: ['heavyOperation'],
  timeout: 60000  // Longer timeout for heavy operations
});

License

MIT License