penpal
Version:
Penpal simplifies communication with iframes, workers, and windows by using promise-based methods on top of postMessage.
235 lines (222 loc) • 7.74 kB
TypeScript
declare class CallOptions {
#private;
readonly transferables?: Transferable[];
readonly timeout?: number;
constructor(options?: {
transferables?: Transferable[];
timeout?: number;
});
}
declare class Reply<T = unknown> {
#private;
readonly value: T;
readonly transferables?: Transferable[];
constructor(value: T, options?: {
transferables?: Transferable[];
});
}
declare const _default: "penpal";
declare const ErrorCodeObj: {
readonly ConnectionDestroyed: "CONNECTION_DESTROYED";
readonly ConnectionTimeout: "CONNECTION_TIMEOUT";
readonly InvalidArgument: "INVALID_ARGUMENT";
readonly MethodCallTimeout: "METHOD_CALL_TIMEOUT";
readonly MethodNotFound: "METHOD_NOT_FOUND";
readonly TransmissionFailed: "TRANSMISSION_FAILED";
};
type ExtractValueFromReply<R> = R extends Reply ? Awaited<R['value']> : R;
/**
* An object representing methods exposed by the remote but that can be called
* locally.
*/
type RemoteProxy<TMethods extends Methods = Methods> = {
[K in keyof TMethods]: TMethods[K] extends (...args: infer A) => infer R ? (...args: [...A, CallOptions?]) => Promise<ExtractValueFromReply<Awaited<R>>> : TMethods[K] extends Methods ? RemoteProxy<TMethods[K]> : never;
};
/**
* An object representing the connection as a result of calling connect().
*/
type Connection<TMethods extends Methods = Methods> = {
/**
* A promise which will be resolved once the connection has been established.
*/
promise: Promise<RemoteProxy<TMethods>>;
/**
* A method that, when called, will disconnect any communication.
* You may call this even before a connection has been established.
*/
destroy: () => void;
};
/**
* Methods to expose to the remote window. May contain nested objects
* with methods as well.
*/
type Methods = {
[index: string]: Methods | Function;
};
/**
* An array of path segments (object property keys) to use to find a method
* within a Methods object. We avoid using a period-delimited string because
* property names can have periods in them which could cause issues.
*/
type MethodPath = string[];
type ErrorCode = typeof ErrorCodeObj[keyof typeof ErrorCodeObj];
type SerializedError = {
name: string;
message: string;
stack?: string;
penpalCode?: ErrorCode;
};
type MessageBase = {
namespace: typeof _default;
channel: string | undefined;
};
type SynMessage = MessageBase & {
type: 'SYN';
participantId: string;
};
type Ack1Message = MessageBase & {
type: 'ACK1';
methodPaths: MethodPath[];
};
type Ack2Message = MessageBase & {
type: 'ACK2';
};
type CallMessage = MessageBase & {
type: 'CALL';
id: string;
methodPath: MethodPath;
args: unknown[];
};
type ReplyMessage = MessageBase & {
type: 'REPLY';
callId: string;
} & ({
value: unknown;
isError?: false;
isSerializedErrorInstance?: false;
} | {
value: unknown;
isError: true;
isSerializedErrorInstance?: false;
} | {
value: SerializedError;
isError: true;
isSerializedErrorInstance: true;
});
type DestroyMessage = MessageBase & {
type: 'DESTROY';
};
type Message = SynMessage | Ack1Message | Ack2Message | CallMessage | ReplyMessage | DestroyMessage;
type Log = (...args: unknown[]) => void;
type MessageHandler = (message: Message) => void;
type InitializeMessengerOptions = {
log?: Log;
validateReceivedMessage: (data: unknown) => data is Message;
};
interface Messenger {
sendMessage: (message: Message, transferables?: Transferable[]) => void;
addMessageHandler: (callback: MessageHandler) => void;
removeMessageHandler: (callback: MessageHandler) => void;
initialize: (options: InitializeMessengerOptions) => void;
destroy: () => void;
}
type Options$3 = {
/**
* Messenger in charge of handling communication with the remote.
*/
messenger: Messenger;
/**
* Methods that may be called by the remote.
*/
methods?: Methods;
/**
* The amount of time, in milliseconds, Penpal should wait
* for a connection to be established before rejecting the connection promise.
*/
timeout?: number;
/**
* A string identifier that disambiguates communication when establishing
* multiple, parallel connections between two participants (e.g., two windows,
* a window and a worker).
*/
channel?: string;
/**
* A function for logging debug messages. Debug messages will only be
* logged when this is defined.
*/
log?: Log;
};
/**
* Attempts to establish communication with the remote.
*/
declare const connect: <TMethods extends Methods>({ messenger, methods, timeout, channel, log, }: Options$3) => Connection<TMethods>;
type Options$2 = {
/**
* The window with which the current window will communicate.
*/
remoteWindow: Window;
/**
* An array of strings or regular expressions defining to which origins
* communication will be allowed. If not provided, communication will be
* restricted to the origin of the current page. You may specify an allowed
* origin of `*` to not restrict communication, but beware the risks of
* doing so.
*/
allowedOrigins?: (string | RegExp)[];
};
/**
* Handles the details of communicating with a child window.
*/
declare class WindowMessenger implements Messenger {
#private;
constructor({ remoteWindow, allowedOrigins }: Options$2);
initialize: ({ log, validateReceivedMessage, }: InitializeMessengerOptions) => void;
sendMessage: (message: Message, transferables?: Transferable[]) => void;
addMessageHandler: (callback: MessageHandler) => void;
removeMessageHandler: (callback: MessageHandler) => void;
destroy: () => void;
}
type Options$1 = {
/**
* The web worker receiving/sending communication from/to the parent window.
* If this messenger is being used within the worker, `worker` should
* typically be set to `self`.
*/
worker: Worker | DedicatedWorkerGlobalScope;
};
/**
* Handles the details of communicating with a child web worker.
*/
declare class WorkerMessenger implements Messenger {
#private;
constructor({ worker }: Options$1);
initialize: ({ validateReceivedMessage }: InitializeMessengerOptions) => void;
sendMessage: (message: Message, transferables?: Transferable[]) => void;
addMessageHandler: (callback: MessageHandler) => void;
removeMessageHandler: (callback: MessageHandler) => void;
destroy: () => void;
}
type Options = {
/**
* The port used to communicate to the other port of the port pair.
*/
port: MessagePort;
};
/**
* Handles the details of communicating on a MessagePort.
*/
declare class PortMessenger implements Messenger {
#private;
constructor({ port }: Options);
initialize: ({ validateReceivedMessage }: InitializeMessengerOptions) => void;
sendMessage: (message: Message, transferables?: Transferable[]) => void;
addMessageHandler: (callback: MessageHandler) => void;
removeMessageHandler: (callback: MessageHandler) => void;
destroy: () => void;
}
declare class PenpalError extends Error {
code: ErrorCode;
constructor(code: ErrorCode, message: string);
}
declare const debug: (prefix?: string) => Log;
export { type Ack1Message, type Ack2Message, type CallMessage, CallOptions, type Connection, type DestroyMessage, ErrorCodeObj as ErrorCode, type InitializeMessengerOptions, type Log, type Message, type MessageHandler, type Messenger, type Methods, PenpalError, PortMessenger, type RemoteProxy, Reply, type ReplyMessage, type SynMessage, WindowMessenger, WorkerMessenger, connect, debug };