Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 14x 8x 8x 14x 14x 15x 15x 14x 14x 2x 2x 16x 8x 16x 4x 8x 2x 5x 3x 3x 5x 3x 3x 5x 5x 5x 5x 2x | /**
* This module provides a pair of classes for creating readable and writable streams
* over a [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).
* function.
*
* @module PostMessage streams
*/
import { isObject } from '@metamask/utils';
import type { OnMessage, PostMessage } from './utils.ts';
import {
BaseDuplexStream,
isDuplexStreamSignal,
makeDuplexStreamInputValidator,
} from '../BaseDuplexStream.ts';
import type { BaseReaderArgs, BaseWriterArgs } from '../BaseStream.ts';
import { BaseReader, BaseWriter } from '../BaseStream.ts';
import { isSignalLike } from '../utils.ts';
import type { Dispatchable } from '../utils.ts';
export type PostMessageTarget = {
addEventListener: (type: 'message', listener: OnMessage) => void;
removeEventListener: (type: 'message', listener: OnMessage) => void;
postMessage: PostMessage;
};
type PostMessageReaderArgs<Read> = BaseReaderArgs<Read> & {
messageTarget: PostMessageTarget;
} & (Read extends MessageEvent
? {
messageEventMode: 'event';
}
: {
messageEventMode?: 'data' | undefined;
});
/**
* A readable stream over a {@link PostMessage} function.
*
* Ignores message events dispatched on its port that contain ports, but otherwise
* expects {@link Dispatchable} values to be posted to its port.
*
* @see {@link PostMessageWriter} for the corresponding writable stream.
*/
export class PostMessageReader<Read> extends BaseReader<Read> {
/**
* Constructs a new {@link PostMessageReader}.
*
* @param options - Options bag for configuring the reader.
* @param options.messageTarget - The target to listen for messages on.
* @param options.validateInput - A function that validates input from the transport.
* @param options.onEnd - A function that is called when the stream ends.
* @param options.messageEventMode - Whether to pass the message event or just the data to the stream.
*/
constructor({
validateInput,
onEnd,
messageTarget,
messageEventMode = 'data',
}: PostMessageReaderArgs<Read>) {
// eslint-disable-next-line prefer-const
let onMessage: OnMessage;
super({
validateInput,
onEnd: async (error) => {
messageTarget.removeEventListener('message', onMessage);
await onEnd?.(error);
},
});
const receiveInput = super.getReceiveInput();
onMessage = (messageEvent) => {
const value =
messageEventMode === 'data' ||
isSignalLike(messageEvent.data) ||
isDuplexStreamSignal(messageEvent.data)
? messageEvent.data
: messageEvent;
receiveInput(value).catch(async (error) => this.throw(error));
};
messageTarget.addEventListener('message', onMessage);
harden(this);
}
}
harden(PostMessageReader);
export type PostMessageEnvelope<Write> = {
payload: Write;
transfer: Transferable[];
};
/**
* Checks if the value is a post message envelope with a payload and transfer array.
*
* @param value - The value to check.
* @returns True if the value is a post message envelope.
*/
const isPostMessageEnvelope = <Write>(
value: unknown,
): value is PostMessageEnvelope<Write> =>
isObject(value) &&
typeof value.payload !== 'undefined' &&
Array.isArray(value.transfer);
/**
* A writable stream over a {@link PostMessage} function.
*
* @see {@link PostMessageReader} for the corresponding readable stream.
*/
export class PostMessageWriter<Write> extends BaseWriter<Write> {
/**
* Constructs a new {@link PostMessageWriter}.
*
* @param messageTarget - The target to post messages to.
* @param options - Options bag for configuring the writer.
* @param options.name - The name of the stream, for logging purposes.
* @param options.onEnd - A function that is called when the stream ends.
*/
constructor(
messageTarget: PostMessageTarget,
{ name, onEnd }: Omit<BaseWriterArgs<Write>, 'onDispatch'> = {},
) {
super({
name,
onDispatch: (value: Dispatchable<Write>) => {
return isPostMessageEnvelope(value)
? messageTarget.postMessage(value.payload, value.transfer)
: messageTarget.postMessage(value);
},
onEnd: async (error) => {
await onEnd?.(error);
},
});
harden(this);
}
}
harden(PostMessageWriter);
type PostMessageDuplexStreamArgs<Read> = PostMessageReaderArgs<Read>;
/**
* A duplex stream over a {@link PostMessage} function.
*
* @see {@link PostMessageReader} for the corresponding readable stream.
* @see {@link PostMessageWriter} for the corresponding writable stream.
*/
export class PostMessageDuplexStream<
Read,
Write = Read,
> extends BaseDuplexStream<
Read,
PostMessageReader<Read>,
Write,
PostMessageWriter<Write>
> {
/**
* Constructs a new {@link PostMessageDuplexStream}.
*
* @param options - Options bag for configuring the duplex stream.
* @param options.messageTarget - The target for sending and receiving messages.
* @param options.validateInput - A function that validates input from the transport.
* @param options.onEnd - A function that is called when the stream ends.
*/
constructor({
messageTarget,
validateInput,
onEnd,
...args
}: PostMessageDuplexStreamArgs<Read>) {
let writer: PostMessageWriter<Write>; // eslint-disable-line prefer-const
const reader = new PostMessageReader<Read>({
...args,
messageTarget,
validateInput: makeDuplexStreamInputValidator(validateInput),
onEnd: async () => {
await onEnd?.();
await writer.return();
},
} as PostMessageReaderArgs<Read>);
writer = new PostMessageWriter<Write>(messageTarget, {
name: 'PostMessageDuplexStream',
onEnd: async () => {
await onEnd?.();
await reader.return();
},
});
super(reader, writer);
}
/**
* Creates and synchronizes a new {@link PostMessageDuplexStream}.
*
* @param args - The options for configuring the duplex stream.
* @returns A synchronized duplex stream.
*/
static async make<Read, Write = Read>(
args: PostMessageDuplexStreamArgs<Read>,
): Promise<PostMessageDuplexStream<Read, Write>> {
const stream = new PostMessageDuplexStream<Read, Write>(args);
await stream.synchronize();
return stream;
}
}
harden(PostMessageDuplexStream);
|