0.1.9 • Published 9 months ago

corpc v0.1.9

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

coRPC: Cross-Origin Remote Procedure Call

A package for promisifying cross-origin messaging (e.g. window.postMessage).

Install

npm install corpc
yarn add corpc
pnpm add corpc

Usage

import { defineProcedures } from "corpc";
import type { RemoteProcedures } from "./remote-procedures";

const localProcedures = defineProcedures({
  procedures,
  postMessage,
  listener,
  addMessageEventListener,
  removeMessageEventListener,
  timeout,
  logger,
}): {
  createRPC,
  cleanUp,
  ...procedures
};

export type LocalProcedures = typeof localProcedures;

const remoteRPC = procedures.createRPC<RemoteProcedures>();

Parameter:

PropertyTypeDefaultDescription
procedures{ [procedureName: string]: (...args: unknown) => any }undefinedThe local functions to be called remotely.
postMessage(message: unknown) => void(message: unknown) => { window.parent.postMessage(message, "*"); }Define the function that sends messages to the other origin. message is what is sent by coRPC and needs to be received by the other origin via the listener prop.
listener(handler: (message: unknown) => void) => Listener(handler) => (event: MessageEvent) => { handler(event.data); }Define the listener handler. This is used in addMessageEventListener and removeMessageEventListener.handler expects the message argument from postMessage.
addMessageEventListener(listener: Listener) => void(listener: (event: MessageEvent) => void) => { window.addEventListener("message", listener); }The local "add message event listener" implementation.
removeMessageEventListener(listener: Listener) => void(listener: (event: MessageEvent) => void) => { window.removeEventListener("message", listener); }The local "remove message event listener" implementation.
timeoutnumber5000RPC timeout. Function will throw if it takes longer than the timeout.
logger(...args: any) => voidundefinedLog function for debug logging.

Returns:

PropertyTypeDescription
createRPC<RemoteProcedures extends Procedures>() => RemoteProcedureProxy<RemoteProcedures>Creates the proxy for calling remote procedures.
cleanUp() => voidRemove message event listener.

Examples

iFrame example

// parent.ts

import { defineProcedures } from "corpc";
import type { IFrameProcedures } from "./iframe";

const iframe: HTMLIFrameElement = new HTMLIFrameElement();

const parentProcedures = defineProcedures({
  procedures: {
    getDataFromParent: (id: string) => {
      return "parent data";
    },
  },
  postMessage: (message) => iframe.contentWindow?.postMessage(message, "*"),
});

export type ParentProcedures = typeof parentProcedures;

const iframeRPC = parentProcedures.createRPC<IFrameProcedures>();
/**
 * ^? const iframeRPC: {
 *      getDataFromIFrame: (id: number) => Promise<string>;
 *    }
 */

const result = await iframeRPC.getDataFromIFrame(10);
// ^? const result: string
// iframe.ts

import { defineProcedures } from "corpc";
import type { ParentProcedures } from "./parent";

const iframeProcedures = defineProcedures({
  procedures: {
    getDataFromIFrame: (id: number) => {
      return "iframe data";
    },
  },
  postMessage: (message: any) => window.top?.postMessage(message, "*"),
});

export type IFrameProcedures = typeof iframeEvents;

const parentRPC = iframeProcedures.createRPC<ParentProcedures>();
/**
 * ^? const parentRPC: {
 *      getDataFromParent: (id: string) => Promise<string>;
 *    }
 */

const result = await parentRPC.getDataFromParent("10");
// ^? const result: string

Figma plugin example

// main.ts

import { defineProcedures } from "corpc";
import type { UiProcedures } from "./ui";

const listeners = Set<(message: unknown) => void>();

const addFigmaEventListener = (listener: (message: unknown) => void) => {
  listeners.add(listener);
};

const removeFigmaEventListener = (listener: (message: unknown) => void) => {
  listeners.delete(listener);
};

figma.ui.onmessage = (message: unknown): void => {
  for (const listener of listeners) {
    listener(message);
  }
};

const mainProcedures = defineProcedures({
  procedures: {
    getCurrentUser: () => figma.currentUser,
    getState: (key: string) => figma.clientStorage.getAsync(key),
    updateState: (key: string, value: any) =>
      figma.clientStorage.setAsync(key, value),
    close: () => figma.ui.close(),
  },
  postMessage: (message) => {
    figma.ui.postMessage(message);
  },
  listener: (handler) => (message: unknown) => {
    handler(message);
  },
  addMessageEventListener: (listener) => {
    addFigmaEventListener(listener);
  },
  removeMessageEventListener: (listener) => {
    removeFigmaEventListener(listener);
  },
});

export type MainProcedures = typeof mainProcedures;

const uiRPC = mainProcedures.createRPC<UiProcedures>();
/**
 * ^? const uiRPC: {}
 */
// ui.ts

import { defineProcedures } from "corpc";
import type { MainProcedures } from "./main";

const uiProcedures = defineProcedures({
  postMessage: (message) => {
    window.parent.postMessage(
      {
        pluginMessage: message,
      },
      "*",
    );
  },
  listener: (handler) => (event: MessageEvent) => {
    handler(event.data.pluginMessage);
  },
});

export type UiProcedures = typeof uiProcedures;

export const mainRPC = uiProcedures.createRPC<MainProcedures>();
/**
 * ^? const mainRPC: {
 *      getCurrentUser: () => Promise<User | null>;
 *      getState: (key: string) => Promise<unknown>;
 *      updateState: (key: string, value: any) => Promise<void>;
 *      close: () => Promise<void>;
 *    }
 */

const user = await mainRPC.getCurrentUser();
// ^? const user: User | null
0.1.9

9 months ago

0.1.8

10 months ago

0.1.7

10 months ago

0.1.6

10 months ago

0.1.5

10 months ago

0.1.4

10 months ago

0.1.3

10 months ago

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago

0.0.1

10 months ago