@formidable-webview/webshell
Version:
🔥 Craft Robust React Native WebView-based components with ease.
152 lines (148 loc) • 4.68 kB
text/typescript
/* eslint-disable no-spaced-func */
import Feature from './Feature';
import type { FeatureClass } from './Feature';
import type {
FeatureDefinition,
PropDefinition,
PropsSpecs,
WebHandlerDefinition,
WebHandlersSpecs
} from './types';
/**
* See {@link FeatureBuilder}.
*
* @typeparam O - A type describing the shape of the JSON-serializable object that will be passed to the Web script.
*
* @public
*/
export interface FeatureBuilderConfig<O extends {}>
extends FeatureDefinition<O> {
/**
* @internal
*/
__propSpecs?: PropsSpecs<any, any>;
/**
* @internal
*/
__webSpecs?: WebHandlersSpecs<any>;
}
/**
* A utility to create feature classes.
*
* @typeparam O - A type describing the shape of the JSON-serializable object that will be passed to the Web script.
* @typeparam S - A type specifying the new properties added to the shell (capabilities to send message to the shell).
* @typeparam W - A type specifying the Web handlers added to the shell (capabilities to send message to the Web script).
*
* @public
*/
export default class FeatureBuilder<
O extends {} = {},
S extends PropsSpecs<any, any> = {},
W extends WebHandlersSpecs<any> = {}
> {
private config: FeatureBuilderConfig<O>;
/**
*
* @param config - An object to specify attributes of the feature.
*/
public constructor(config: FeatureBuilderConfig<O>) {
this.config = config;
if (typeof config.script === 'function') {
throw new TypeError(
'[FeatureBuilder]: config.script must be a string. If you are trying to import a ' +
'webjs file such as in the docs, you need to setup babel-plugin-inline-import. ' +
'See our guide here: https://formidable-webview.github.io/webshell/docs/tooling#babel'
);
}
}
/**
* Instruct that the shell will receive events from the Web, and provide a
* new handler prop for that purpose.
*
* @param propName - The name of the handler prop added to the shell.
* It is advised to follow the convention of prefixing all these handlers
* with `onDOM` or `onWeb` to avoid collisions with `WebView` own props.
* @param eventId - A unique identifier for the event received by the shell.
* If none is provided, fallback to `"default"`.
*
* @typeparam N - A type to define the name of the prop.
* @typeparam P - A type describing the shape of payloads sent to shell handlers.
*/
withShellHandler<N extends string, P>(
propName: N,
eventId: string = 'default'
) {
const propDefinition: PropDefinition<N, (p: P) => void> = {
eventId,
name: propName,
featureIdentifier: this.config.identifier,
type: 'handler'
};
const propSpec = {
[propName]: propDefinition
} as PropsSpecs<N, typeof propDefinition>;
return new FeatureBuilder<O, S & PropsSpecs<N, (p: P) => void>, W>({
...this.config,
__propSpecs: {
...(this.config.__propSpecs || {}),
...propSpec
}
});
}
/**
* Instruct that the Web script will receive events from the shell.
* See {@link WebshellInvariantProps.webHandleRef}, {@link WebHandle.postMessageToWeb} and {@link WebjsContext.onShellMessage}.
*
* @param eventId - A unique identifier for the event received by the Web script.
*
* @typeparam I - A type for the event identifier.
* @typeparam P - A type describing the shape of payloads sent to Web handlers.
*/
withWebHandler<I extends string = string, P = undefined>(eventId: I) {
return new FeatureBuilder<
O,
S,
W & { [k in I]: WebHandlerDefinition<I, P> }
>({
...this.config,
__webSpecs: {
...(this.config.__webSpecs || {}),
[eventId]: { async: false, eventId }
}
});
}
/**
* Assemble this builder object into a feature class.
*/
build(): FeatureClass<O, S, W> {
const {
script,
identifier: featureIdentifier,
__propSpecs: propSpecs,
__webSpecs: webSpecs,
defaultOptions
} = this.config;
const ctor = class extends Feature<O, S, W> {
static identifier = featureIdentifier;
constructor(...args: O extends Partial<O> ? [] | [O] : [O]) {
super(
{
script,
identifier: featureIdentifier,
defaultOptions,
propSpecs: (propSpecs || []) as S,
webSpecs: (webSpecs || {}) as W
},
(args[0] || {}) as O
);
}
};
Object.defineProperty(ctor, 'displayName', {
configurable: true,
enumerable: false,
writable: false,
value: `Feature(${featureIdentifier})`
});
return ctor;
}
}