首次提交
This commit is contained in:
+52
@@ -0,0 +1,52 @@
|
||||
import { IBrokerEvent, IErrorNotification, IErrorResponse, IReceiver, IRequest, IWorkerDefinition } from '../interfaces';
|
||||
import { TMessageReceiverWithParams, TMessageReceiverWithoutParams, TWorkerImplementation } from '../types';
|
||||
import { renderMethodNotFoundError, renderMissingResponseError, renderUnexpectedResultError } from './error-renderers';
|
||||
|
||||
export const createMessageHandler = <WorkerDefinition extends IWorkerDefinition>(
|
||||
receiver: IReceiver,
|
||||
workerImplementation: TWorkerImplementation<WorkerDefinition>
|
||||
) => {
|
||||
return async ({ data: { id, method, params } }: IBrokerEvent<WorkerDefinition>) => {
|
||||
const messageHandler = workerImplementation[method];
|
||||
|
||||
try {
|
||||
if (messageHandler === undefined) {
|
||||
throw renderMethodNotFoundError(method);
|
||||
}
|
||||
|
||||
const response =
|
||||
params === undefined
|
||||
? (messageHandler as TMessageReceiverWithoutParams<WorkerDefinition[typeof method]['response']>)()
|
||||
: (
|
||||
messageHandler as TMessageReceiverWithParams<
|
||||
WorkerDefinition[typeof method]['params'],
|
||||
WorkerDefinition[typeof method]['response']
|
||||
>
|
||||
)(params);
|
||||
|
||||
if (response === undefined) {
|
||||
throw renderMissingResponseError(method);
|
||||
}
|
||||
|
||||
const synchronousResponse = response instanceof Promise ? await response : response;
|
||||
|
||||
if (id === null) {
|
||||
if (synchronousResponse.result !== undefined) {
|
||||
throw renderUnexpectedResultError(method);
|
||||
}
|
||||
} else {
|
||||
if (synchronousResponse.result === undefined) {
|
||||
throw renderUnexpectedResultError(method);
|
||||
}
|
||||
|
||||
const { result, transferables = [] } = <IRequest['response']>synchronousResponse;
|
||||
|
||||
receiver.postMessage({ id, result }, transferables);
|
||||
}
|
||||
} catch (err) {
|
||||
const { message, status = -32603 } = err;
|
||||
|
||||
receiver.postMessage(<IErrorNotification | IErrorResponse>{ error: { code: status, message }, id });
|
||||
}
|
||||
};
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
const JSON_RPC_ERROR_CODES = { INTERNAL_ERROR: -32603, INVALID_PARAMS: -32602, METHOD_NOT_FOUND: -32601 };
|
||||
|
||||
const createErrorWithMessageAndStatus = (message: string, status: number) => Object.assign(new Error(message), { status });
|
||||
|
||||
export const renderMethodNotFoundError = (method: string) =>
|
||||
createErrorWithMessageAndStatus(`The requested method called "${method}" is not supported.`, JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND);
|
||||
|
||||
export const renderMissingResponseError = (method: string) =>
|
||||
createErrorWithMessageAndStatus(
|
||||
`The handler of the method called "${method}" returned no required result.`,
|
||||
JSON_RPC_ERROR_CODES.INTERNAL_ERROR
|
||||
);
|
||||
|
||||
export const renderUnexpectedResultError = (method: string) =>
|
||||
createErrorWithMessageAndStatus(
|
||||
`The handler of the method called "${method}" returned an unexpected result.`,
|
||||
JSON_RPC_ERROR_CODES.INTERNAL_ERROR
|
||||
);
|
||||
|
||||
export const renderUnknownPortIdError = (portId: number) =>
|
||||
createErrorWithMessageAndStatus(
|
||||
`The specified parameter called "portId" with the given value "${portId}" does not identify a port connected to this worker.`,
|
||||
JSON_RPC_ERROR_CODES.INVALID_PARAMS
|
||||
);
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
import { generateUniqueNumber } from 'fast-unique-numbers';
|
||||
import { IDefaultWorkerDefinition, IReceiver, IWorkerDefinition } from '../interfaces';
|
||||
import { TDestroyWorkerFunction, TWorkerImplementation } from '../types';
|
||||
import { renderUnknownPortIdError } from './error-renderers';
|
||||
import { isSupportingTransferables } from './is-supporting-transferables';
|
||||
|
||||
const DESTROY_WORKER_FUNCTIONS: Map<number, TDestroyWorkerFunction> = new Map();
|
||||
|
||||
export const extendWorkerImplementation = <WorkerDefinition extends IWorkerDefinition>(
|
||||
createWorker: (receiver: IReceiver, workerImplementation: TWorkerImplementation<WorkerDefinition>) => TDestroyWorkerFunction,
|
||||
partialWorkerImplementation: TWorkerImplementation<WorkerDefinition>,
|
||||
isSupportedFunction: () => boolean | Promise<boolean>
|
||||
): TWorkerImplementation<WorkerDefinition> & TWorkerImplementation<IDefaultWorkerDefinition> => ({
|
||||
...partialWorkerImplementation,
|
||||
connect: ({ port }: { port: MessagePort }) => {
|
||||
port.start();
|
||||
|
||||
const destroyWorker = createWorker(port, partialWorkerImplementation);
|
||||
const portId = generateUniqueNumber(DESTROY_WORKER_FUNCTIONS);
|
||||
|
||||
DESTROY_WORKER_FUNCTIONS.set(portId, () => {
|
||||
destroyWorker();
|
||||
port.close();
|
||||
DESTROY_WORKER_FUNCTIONS.delete(portId);
|
||||
});
|
||||
|
||||
return { result: portId };
|
||||
},
|
||||
disconnect: ({ portId }: { portId: number }) => {
|
||||
const destroyWorker = DESTROY_WORKER_FUNCTIONS.get(portId);
|
||||
|
||||
if (destroyWorker === undefined) {
|
||||
throw renderUnknownPortIdError(portId);
|
||||
}
|
||||
|
||||
destroyWorker();
|
||||
|
||||
return { result: null };
|
||||
},
|
||||
isSupported: async () => {
|
||||
const isSelfSupported = await isSupportingTransferables();
|
||||
|
||||
if (isSelfSupported) {
|
||||
const result = isSupportedFunction();
|
||||
const synchronousResult = result instanceof Promise ? await result : result;
|
||||
|
||||
return { result: synchronousResult };
|
||||
}
|
||||
|
||||
return { result: false };
|
||||
}
|
||||
});
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
// Bug #1: Safari does currently not support to use transferables.
|
||||
|
||||
export const isSupportingTransferables = () =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const arrayBuffer = new ArrayBuffer(0);
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
port1.onmessage = ({ data }) => resolve(data !== null);
|
||||
port2.postMessage(arrayBuffer, [arrayBuffer]);
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { IBrokerMessage } from './broker-message';
|
||||
import { IWorkerDefinition } from './worker-definition';
|
||||
|
||||
export interface IBrokerEvent<WorkerDefinition extends IWorkerDefinition> extends Event {
|
||||
data: IBrokerMessage<WorkerDefinition>;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { IWorkerDefinition } from './worker-definition';
|
||||
|
||||
export interface IBrokerMessage<WorkerDefinition extends IWorkerDefinition> {
|
||||
id: null | number;
|
||||
|
||||
method: Extract<keyof WorkerDefinition, string>;
|
||||
|
||||
params: WorkerDefinition[Extract<keyof WorkerDefinition, string>]['params'];
|
||||
}
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
import { IWorkerDefinition } from './worker-definition';
|
||||
|
||||
export interface IDefaultWorkerDefinition extends IWorkerDefinition {
|
||||
connect: {
|
||||
params: {
|
||||
port: MessagePort;
|
||||
};
|
||||
|
||||
response: {
|
||||
result: number;
|
||||
};
|
||||
};
|
||||
|
||||
disconnect: {
|
||||
params: {
|
||||
portId: number;
|
||||
};
|
||||
|
||||
response: {
|
||||
result: null;
|
||||
};
|
||||
};
|
||||
|
||||
isSupported: {
|
||||
response: {
|
||||
result: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { IError } from './error';
|
||||
|
||||
export interface IErrorNotification {
|
||||
error: IError;
|
||||
|
||||
id: null;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { IError } from './error';
|
||||
|
||||
export interface IErrorResponse {
|
||||
error: IError;
|
||||
|
||||
id: null;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export interface IError {
|
||||
code: number;
|
||||
|
||||
message: string;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export * from './broker-event';
|
||||
export * from './broker-message';
|
||||
export * from './default-worker-definition';
|
||||
export * from './error';
|
||||
export * from './error-notification';
|
||||
export * from './error-response';
|
||||
export * from './notification';
|
||||
export * from './receiver';
|
||||
export * from './request';
|
||||
export * from './value-array';
|
||||
export * from './value-map';
|
||||
export * from './worker-definition';
|
||||
export * from './worker-error-message';
|
||||
export * from './worker-result-message';
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { TValue } from '../types';
|
||||
|
||||
export interface INotification {
|
||||
params: TValue;
|
||||
|
||||
response: {
|
||||
result: undefined;
|
||||
};
|
||||
|
||||
transferables?: Transferable[];
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export interface IReceiver {
|
||||
addEventListener: typeof addEventListener;
|
||||
|
||||
postMessage: typeof postMessage;
|
||||
|
||||
removeEventListener: typeof removeEventListener;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { TValue } from '../types';
|
||||
|
||||
export interface IRequest {
|
||||
params?: TValue[] | { [key: string]: TValue };
|
||||
|
||||
response: {
|
||||
result: TValue;
|
||||
|
||||
transferables?: Transferable[];
|
||||
};
|
||||
|
||||
transferables?: Transferable[];
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TValue } from '../types';
|
||||
|
||||
export interface IValueArray extends Array<TValue> {
|
||||
// tslint:disable-line:no-empty-interface
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TValue } from '../types';
|
||||
|
||||
export interface IValueMap {
|
||||
[key: string]: TValue;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { TMessage } from '../types';
|
||||
|
||||
export interface IWorkerDefinition {
|
||||
[method: string]: TMessage;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { IError } from './error';
|
||||
|
||||
export interface IWorkerErrorMessage {
|
||||
error: IError;
|
||||
|
||||
id: null | number;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { TValue } from '../types';
|
||||
|
||||
export interface IWorkerResultMessage {
|
||||
id: null | number;
|
||||
|
||||
result: TValue;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { createMessageHandler } from './helpers/create-message-handler';
|
||||
import { extendWorkerImplementation } from './helpers/extend-worker-implementation';
|
||||
import { isSupportingTransferables } from './helpers/is-supporting-transferables';
|
||||
import { IReceiver, IWorkerDefinition } from './interfaces';
|
||||
import { TDestroyWorkerFunction, TWorkerImplementation } from './types';
|
||||
|
||||
/*
|
||||
* @todo Explicitly referencing the barrel file seems to be necessary when enabling the
|
||||
* isolatedModules compiler option.
|
||||
*/
|
||||
export * from './interfaces/index';
|
||||
export * from './types/index';
|
||||
|
||||
export const createWorker = <WorkerDefinition extends IWorkerDefinition>(
|
||||
receiver: IReceiver,
|
||||
workerImplementation: TWorkerImplementation<WorkerDefinition>,
|
||||
isSupportedFunction: () => boolean | Promise<boolean> = () => true
|
||||
): TDestroyWorkerFunction => {
|
||||
const fullWorkerImplementation = extendWorkerImplementation<WorkerDefinition>(createWorker, workerImplementation, isSupportedFunction);
|
||||
const messageHandler = createMessageHandler(receiver, fullWorkerImplementation);
|
||||
|
||||
receiver.addEventListener('message', messageHandler);
|
||||
|
||||
return () => receiver.removeEventListener('message', messageHandler);
|
||||
};
|
||||
|
||||
export { isSupportingTransferables as isSupported };
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"isolatedModules": true
|
||||
},
|
||||
"extends": "tsconfig-holy-grail/src/tsconfig-web-worker"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export type TDestroyWorkerFunction = () => void;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export * from './destroy-worker-function';
|
||||
export * from './message';
|
||||
export * from './message-receiver';
|
||||
export * from './message-receiver-with-params';
|
||||
export * from './message-receiver-without-params';
|
||||
export * from './typed-array';
|
||||
export * from './value';
|
||||
export * from './value-map';
|
||||
export * from './worker-definition';
|
||||
export * from './worker-implementation';
|
||||
export * from './worker-message';
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
import { TMessage } from './message';
|
||||
|
||||
export type TMessageReceiverWithParams<Params extends TMessage['params'], Response extends TMessage['response']> = (
|
||||
params: Params
|
||||
) => Response | Promise<Response>;
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import { TMessage } from './message';
|
||||
|
||||
export type TMessageReceiverWithoutParams<Message extends TMessage['response']> = () => Message | Promise<Message>;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { TMessage } from './message';
|
||||
import { TMessageReceiverWithParams } from './message-receiver-with-params';
|
||||
import { TMessageReceiverWithoutParams } from './message-receiver-without-params';
|
||||
|
||||
export type TMessageReceiver<Message extends TMessage> = Message['params'] extends undefined
|
||||
? TMessageReceiverWithoutParams<Message['response']>
|
||||
: TMessageReceiverWithParams<Message['params'], Message['response']>;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { INotification, IRequest } from '../interfaces';
|
||||
|
||||
export type TMessage = INotification | IRequest;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
export type TTypedArray =
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| Int8Array
|
||||
| Int16Array
|
||||
| Int32Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Uint16Array
|
||||
| Uint32Array;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { IValueMap } from '../interfaces';
|
||||
|
||||
export type TValueMap<ValueMap extends IValueMap = IValueMap> = {
|
||||
[P in keyof ValueMap]: ValueMap[P];
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { IValueArray } from '../interfaces';
|
||||
import { TTypedArray } from './typed-array';
|
||||
import { TValueMap } from './value-map';
|
||||
|
||||
export type TValue = boolean | null | number | string | IValueArray | RegExp | TTypedArray | TValueMap | Transferable;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { IDefaultWorkerDefinition, IWorkerDefinition } from '../interfaces';
|
||||
|
||||
export type TWorkerDefinition<WorkerDefinition extends IWorkerDefinition> = WorkerDefinition & IDefaultWorkerDefinition;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { IWorkerDefinition } from '../interfaces';
|
||||
import { TMessageReceiver } from './message-receiver';
|
||||
|
||||
export type TWorkerImplementation<WorkerDefinition extends IWorkerDefinition> = {
|
||||
[P in keyof WorkerDefinition]: TMessageReceiver<WorkerDefinition[P]>;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { IWorkerErrorMessage, IWorkerResultMessage } from '../interfaces';
|
||||
|
||||
export type TWorkerMessage = IWorkerErrorMessage | IWorkerResultMessage;
|
||||
Reference in New Issue
Block a user