rpcchannel
Version:
Easy RPC with permission controls
221 lines (220 loc) • 8.75 kB
TypeScript
/**
* @author Nathan Pennie <kb1rd@kb1rd.net>
*/
/** */
import EventEmitter from 'eventemitter3';
import { MultistringAddress, WildcardMultistringAddress, AddressMap } from './addrmap';
import { ChainedAccessController, AccessController, OptAccessPolicy, CanCallFunction, RequiresPermissions, PermissionedAccessCanFunction, CanCallOpts } from './accesscontrol';
import { SerializableData, SerializedData } from './serializer';
export declare const RpcFunctionAddress: unique symbol;
export declare const RpcRemappedFunction: unique symbol;
interface WithValidAddressKey {
[RpcFunctionAddress]?: WildcardMultistringAddress;
}
declare type RpcResult = SerializableData | Promise<SerializableData> | AsyncGenerator<SerializableData, void, void>;
/**
* A destination function for Remote Procedure Calls (RPCs).
* @param src The source `RpcChannel`
* @param wildcards Any wildcards that were used in resolution of this function
*/
export interface RpcFunction extends WithValidAddressKey {
(src: RpcChannel, wildcards: string[], ...args: SerializedData[]): RpcResult;
[RpcRemappedFunction]?: RpcFunction;
[CanCallFunction]?: PermissionedAccessCanFunction;
[RequiresPermissions]?: Iterable<string>;
}
export declare function RpcAddress(address: WildcardMultistringAddress): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
export declare function RemapArguments(mapping: ('pass' | 'drop' | 'expand')[], key?: string | symbol | number): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
/**
* Sent between threads/workers/tabs/domains/whatever to carry RPCs and
* associated data.
*/
export interface RpcMessage {
to: MultistringAddress;
args: SerializedData[];
return_addr?: MultistringAddress;
return_type?: 'promise' | 'generator';
}
export declare namespace RpcMessage {
const Schema: {
type: string;
properties: {
to: {
type: string;
items: {
type: string;
};
};
args: {
type: string;
};
return_addr: {
type: string;
items: {
type: string;
};
};
return_type: {
type: string;
enum: string[];
};
};
required: string[];
};
}
export declare type BaseRegisteredObject = {
[key: string]: WithValidAddressKey;
};
/**
* Anything where RPC functions can be registered and unregistered.
*/
interface HandleRegistry {
/**
* Registers a handler for incoming data
* @param address Address for the handle
* @param func Function to call when triggered
*/
register(address: WildcardMultistringAddress, func: RpcFunction): void;
/**
* Unregisters a handler for incoming data
* @param address Address for the handle
*/
unregister(address: WildcardMultistringAddress): void;
/**
* Registers member functions of a class (marked with the `RpcAddress`
* decorator)
*/
registerAll(obj: BaseRegisteredObject): void;
/**
* Unregisters member functions of a class (marked with the `RpcAddress`
* decorator). Note that if any functions were removed from the class after
* it was registered, those will not be unregistered.
*/
unregisterAll(obj: BaseRegisteredObject): void;
}
/**
* Where RPC handles are registered to a particular address. This can be
* re-used between different `RpcChannel`s.
*/
export declare class RpcHandlerRegistry extends ChainedAccessController implements HandleRegistry {
map: AddressMap<RpcFunction>;
return_seq_id: number;
constructor();
register(address: WildcardMultistringAddress, func: RpcFunction): void;
unregister(address: WildcardMultistringAddress): void;
registerAll(base: BaseRegisteredObject): void;
unregisterAll(base: BaseRegisteredObject): void;
clear(): void;
nextSeqAddr(): MultistringAddress;
}
export interface RpcAccessor {
(...args: SerializableData[]): Promise<SerializedData>;
[key: string]: RpcAccessor;
}
/**
* Thrown when a return from a function call reaches a different RpcChannel
* than it was sent from. This **may** indicate a security issue due to re-used
* recieve callbacks.
*/
export declare class InvalidChannelError extends Error {
readonly name = "InvalidChannelError";
}
export declare class AccessDeniedError extends Error {
readonly name = "AccessDeniedError";
}
export declare class ForwardedError extends Error {
}
interface RpcChannelOpts {
/**
* If a keepalive has not been received for this much time, assume that this
* `RpcChannel` has been closed.
*/
timeout?: number;
/**
* The interval with which to send keep-alives. This must be shorter than the
* timeout interval on the receiving end.
*/
keep_alive_interval?: number;
/**
* Waits to start the channel until a message is received. This should only
* be used on the end that is started first: Such as an `iframe` parent, a
* worker's host, or a tab's creator.
*/
await_first_msg?: boolean;
}
export declare enum RpcState {
INACTIVE = 0,
ACTIVE = 1,
CLOSED = 2
}
/**
* A wrapper class for functions to perform remote procedure calls.
*/
export declare class RpcChannel extends EventEmitter implements HandleRegistry, AccessController {
protected readonly c_send: (msg: RpcMessage, xfer: Transferable[]) => void;
protected readonly default_policy: boolean;
reg: RpcHandlerRegistry;
protected readonly _opts: RpcChannelOpts;
readonly _i_reg: RpcHandlerRegistry;
access_controller?: AccessController;
active_timeout?: number;
active_keepalive?: number;
protected _state: RpcState;
/**
* @param c_send The function to send over whatever transport is used.
* @param reg The handle registry. This can be changed later.
*/
constructor(c_send: (msg: RpcMessage, xfer: Transferable[]) => void, default_policy?: boolean, reg?: RpcHandlerRegistry, _opts?: RpcChannelOpts);
get opts(): Readonly<NonNullable<RpcChannelOpts>>;
get state(): RpcState;
resetTimeout(): void;
sendKeepAlive(): void;
resetKeepAlive(): void;
setTimeout(timeout?: number, keep_alive_interval?: number): void;
setAwaitFirstMsg(should: boolean): void;
_stateChange(state: RpcState): void;
start(): Promise<void>;
close(send?: boolean): void;
stop: () => void;
register(address: WildcardMultistringAddress, func: RpcFunction): void;
unregister(address: WildcardMultistringAddress): void;
registerAll(obj: BaseRegisteredObject): void;
unregisterAll(obj: BaseRegisteredObject): void;
can(addr: MultistringAddress, opts: CanCallOpts): OptAccessPolicy;
/**
* Sends data to a particular handle. Because there is no `await` for the
* other side to process this, the `send` function should be used for pushing
* data only since multiple messages may be sent before the other side gets
* around to processing them.
* @param to Address to send data to
* @param args Data to send
* @param return_addr The address of the return field. This is used for full
* transactions, such as function calls
*/
send(to: MultistringAddress, args?: SerializableData[], return_addr?: MultistringAddress, return_type?: 'promise' | 'generator'): void;
/**
* Calls a handle and awaits the return value.
* @param to Handle to call
* @param args Arguments to pass through
* @returns A promise that will return when the call is completed. This will
* throw an error with the message `Channel closed` if the channel is closed
* before a response is received.
*/
call(to: MultistringAddress, args?: SerializableData[]): Promise<SerializedData>;
/**
* Returns an async generator. This supports only `yield`ing values: ATM,
* returned values and `yield`ed arguments are not supported. **You also must
* manually deallocate the generator once you're done!** Yes, manual memory
* management. If you don't manually deallocate, the listeners on both ends
* will remain allocated leading to memory leaks. To deallocate, call the
* `return` or `throw` functions on the generator.
*/
generate(to: MultistringAddress, args?: SerializableData[]): AsyncGenerator<SerializedData, void, void>;
get call_obj(): RpcAccessor;
/**
* Call this when a new message is recieved to process it.
* @param val Incoming message
*/
receive(val: RpcMessage): void;
}
export {};