0.9.1 • Published 11 months ago

pleat v0.9.1

Weekly downloads
-
License
MIT
Repository
-
Last release
11 months ago

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
}
0.9.1

11 months ago

1.0.0-next.30

11 months ago

1.0.0-next.29

12 months ago

1.0.0-next.28

12 months ago

1.0.0-next.27

12 months ago

0.9.0

12 months ago

0.0.9

12 months ago

1.0.0-next.26

12 months ago

1.0.0-next.25

12 months ago

1.0.0-next.24

12 months ago

1.0.0-next.23

12 months ago

1.0.0-next.22

12 months ago

1.0.0-next.21

12 months ago

1.0.0-next.20

12 months ago

1.0.0-next.19

12 months ago

1.0.0-next.18

12 months ago

1.0.0-next.17

12 months ago

1.0.0-next.16

12 months ago

1.0.0-next.15

12 months ago

1.0.0-next.14

12 months ago

1.0.0-next.13

12 months ago

1.0.0-next.12

12 months ago

1.0.0-next.11

12 months ago

1.0.0-next.10

12 months ago

1.0.0-next.9

12 months ago

1.0.0-next.8

12 months ago

1.0.0-next.7

12 months ago

0.0.4

12 months ago

1.0.0-next.6

12 months ago

1.0.0-next.5

12 months ago

1.0.0-next.4

12 months ago

1.0.0-next.3

12 months ago

1.0.0-next.2

12 months ago

1.0.0-next.1

12 months ago

1.0.0-next.0

12 months ago

0.0.3

12 months ago

0.0.2

12 months ago

0.0.1

12 months ago