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 🙏

© 2024 – Pkg Stats / Ryan Hefner

pleat

v0.9.1

Published

<h3>A Framework for Micro Clients and Sandboxed Scripts</h3>

Downloads

34

Readme

Pleat

  • Tiny Library (2kb brotli compressed)
  • No external dependencies
  • Handles iframe resizing
  • RPC and Proxy API makes it SRI safe
  • Fully sandboxed
  • Supports cross-origin and same-origin iframes
  • Self hostable
  • Open source

Basics

Pleat facilitates the integration between a Client and a Widget or Service.

Client

The Client is the website that wants to use a Widget or Service.

Examples of a Client are a website that wants to use:

  • A "syntax highlighting" Widget for their blog site
  • A payment processing Widget
  • A pop-over login page Widget offered by a managed authentication provider

Widget

A Widget is a micro client loaded as an iframe. Pleat aims to make Widgets function like pseudo "Web Components" that can be consumed as dynamic third party sources without exposing the Client or Widget provider to the security vulnerabilities of running dynamic scripts on the root Document context.

This is useful for cases where the Widget wants to be isolated from the Client for security reasons (example payment processors or authentication login pages) or where the Client wants to be able to use a UI component that requires access to first party local storage and HTTP APIs (like a chat client or "comments section" service)

Service

A Service is a sandboxed script that can be launched by the Client and any Widget. A Service does not have access to the Window/Document of the Client/Widget though it's able to export a programmatic API (method).

This is useful for scenarios where a Client wants to use a dynamic third party script without exposing their site to vulnerabilities and allows for use cases where functionality/state needs to be shared between Widgets.

The exported API is implemented in the consumer using an RPC protocol and JavaScript Proxy objects wrappers. This allows the consumer to lock the loaded client script using an SRI hash while still allowing the Service provider to dynamically update their service, expanding functionality.

Depending on the requirements of the service, services can be spawned as web workers, hidden iframes or wasm modules.

Usage

The Client/Consumer

The Client initializes a Widget using the createWidget method which returns back a Widget instance they can interact with.

<html>
<body>
  <h1>Hello and Welcome to my Site!</h1>
  
  <script type = module>
    import Pleat from 'pleat/client'

    // Start the pleat engine and create a Widget
    const yourWidget = Pleat.createWidget({
      url: 'https://your-iframe.com/iframe.html'
    })

    // Adds the iframe to the body 
    yourWidget.appendTo({ selector: 'body' }) 

    // The Client can run methods registered in the Widget
    const result = await yourWidget.foo() 
    console.log(result) // "Hi from foo"
  </script>
</body>
</html>

Widgets

The Widget is a normal iframe that registers itself using the Pleat Widget API.

From within the iframe

<html>
<body>
  <h1>Hello World Widget!</h1>
  <button>Close!</button>
  
  <script type = module>
    import Pleat from 'pleat/widget'

    // The Widget API
    const widget = Pleat.initWidget({
      // Automatically resize iframe when contents change
      autoResize: true,
    })

    // Register a method for the Client
    widget.registerMethod('foo', () => {
      return "Hi from foo"
    })

    document.querySelector('button').onclick = () => {
      // Close the widget
      widget.close()
    }

    // Widget will show up at this point
    widget.ready()
  </script>
</body>
</html>

Self Hosting

To tie the entities together, Pleat uses a central router called the Engine. This is an invisible iframe used to coordinate actions of Widgets and Services, manage child Widgets.

A self hosted Engine can extend the Client and Widget APIs for a specific use case (e.g. a payment processor).

The engine must be hosted on a domain, for instance: https://example.com/pleat/engine/index.html. Below is an example of an Engine that registers methods on the Client.

<html>
<body> 
  <script type = module>
    import Pleat from 'pleat/engine'

    // The Engine API
    const engine = Pleat.initEngine()

    // Register a method for the Client
    engine.registerClientMethod('initChat', async ({ accountSecret }) => {
      // Maybe make a request to your back end to create a chat session
      const response = await fetch(`https://example.com/api/start-chat?secret=${accountSecret}`).then(r => r.json())

      // Create a widget on behalf of the Client with a chat window
      const widget = engine.createWidget({
        url: 'https://example.com/pleat/widget/chat/index.html',
        config: response
      })

      // Append chat Widget to the Client "body"
      widget.appendTo('body')
    })

    // Engine will begin communicating with the Client at this pont
    engine.connect()
  </script>
</body>
</html>

Where the Client would consume your self hosted Engine like

<html>
<body> 
  <h1>Hello and Welcome to my Site!</h1>

  <script type = module>
    import Pleat from 'pleat/client'

    // The Engine API
    const engine = Pleat.init({
      engineUrl: 'https://example.com/pleat/engine/index.html'
    })

    // Will run the "initChat" method specified by the Engine
    await engine.initChat({
      accountSecret: '1234567890'
    })
  </script>
</body>
</html>

Interfaces

Client

export interface IClientBase {
  // Create a Widget
  createWidget<T = DefaultWidget>(options: CreateWidgetOptions): IWidget<T>
  
  // Communicate to Engine
  on<T = unknown>(eventName: string, callback: EventCallback<T>): DisposeFunc
  runMethod(methodName: string, args: any[]): Promise<any>
  
  // Close all widgets and shutdown Pleat
  shutdown(): void
}

Widget

export interface IWidgetBase {
  // Begin sending/receiving messages
  connect(): void

  // Communicate to Parent
  emit(eventName: string, data?: any): void
  registerMethod<T extends any[], U = any>(methodName: string, action: WidgetMethod<T, U>): void

  // Communicate to Client
  emitClient(eventName: string, data?: any): void
  
  // Set external styles of iframe
  putStyles(styles: CSSStyles): void
  
  // Create nested Widget
  createChildWidget<T = DefaultWidget>(options: CreateWidgetOptions): IChildWidget<T>
  
  // Run method exposed by engine
  runMethod(methodName: string, args: any[]): Promise<any>
  on<T = unknown>(eventName: string, callback: EventCallback<T>): DisposeFunc
  
  // Close Widget
  close(): void
}

Engine

export interface IEngine {
  // Create a Widget on the Client controlled by the Engine
  createWidget<T = DefaultWidget>(options: CreateWidgetOptions): IWidget<T>
  
  // Communicate to Client
  emitClient(eventName: string, data?: any): void
  registerClientMethod<T extends any[], U = any>(methodName: string[], action: RegisteredMethod<T, U>): void
  
  // Communicate to Widgets
  emitWidgets(eventName: string, data?: any): void
  registerWidgetMethod<T extends any[], U = any>(methodName: string[], action: RegisteredMethod<T, U>): void
  
  // Close all widgets and shutdown Pleat
  shutdown(): void
}