Implementing Web Features
Features can set up communication in both directions, Web to native and native to Web. We will approach the two in distinct examples, but a feature can support both at the same time.
Web to Native Communication
To overview Web to native communication, we will take a look at a simplified implementation of HandleLinkPressFeature.
A feature has two facets:
- The Web Script, which embodies behavior in the Web environment;
- The Feature Class, which tackles integration with the shell.
disclaimer
This implementation is naive and doesn't handle inner click events. If you need such a feature, use our official HandleLinkPressFeature.
Web Script
Let's start with the Web script, the code which will run in the WebView.
Every Web script top declaration must be a function taking one argument. The shape of this argument is depicted in WebjsContext definition. This context object is the central API to interact with the shell from the Web environment.
Below is the same snippet with highlighted lines each matching a different context usage:
context.options
is the result of shallow-mergingdefaultOptions
with user-provided options during feature instantiation.context.postMessageToShell
will send an event which will be intercepted by the shell. If an event handler has been registered for this feature and passed as a prop to the shell, it will be invoked with the message content.context.warn
andcontext.info
will send a log event to the shell whenwebshellDebug
prop is set totrue
. The shell will log the message in the native environment for quick feedback during development.context.makeCallbackSafe
guarantees that any error raised in the body of the callback will be intercepted by the context API and routed to the shell for quick feedback during development.
Building a Feature Class
Now let's dive into the native-side. As you can see, the definition is relatively terse:
- Typescript
- JavaScript
Below is a recap of all the symbols present in this snippet:
Symbol | Description |
---|---|
linkPressScript | Behavior in the Web environment (string ). |
defaultOptions | Default options for this feature (object ). |
"onDOMLinkPress" | The name of the handler prop which will be available in the shell (string ). |
"org.myorg/webshell.link-press" | A unique identifier for this feature (string ). |
LinkPressOptions | [Typescript] The shape of options the feature class can be instantiated with. |
LinkPressTarget | [Typescript] The type of payload the handler prop will receive. |
It is also worth noting that invoking withShellHandler
is not an option:
- It register the handler to the shell, otherwise events fired by the below Web script will not be routed to the corresponding handler prop (
onDOMLinkPress
). - When implemented in typescript, type generation will add the shell handler name and payload type to the the feature, which will be grabbed by the resulting shell prop type.
important
Be advised that linkPressScript
must be a string
. In the snippet, we are using babel-plugin-inline-import
to import the Web script as string
, see the tooling guide;
important
Don't forget to invoke build
method, otherwise you'll end up with a FeatureBuilder instance.
Using the Feature
- Typescript
- JavaScript
Native to Web Communication
Until now, we have previewed how to send messages from the Web environment to the shell.
Let's say we now want to pass an apiToken
prop to our Web script so that it
execute some authentication logic when the controlling component prop changes.
To allow communication from the shell to the Web environment, we will need to do the following:
- Declare a Web handler with the
FeatureBuilder
; - Register a handler in the Web script;
- Use a WebHandle from the shell to send
apiTokens
.
Declaring Web Handler
We need to register a handler associated with the feature to allow the
shell to convey messages to the appropriate destination.
Let's use FeatureBuilder.withWebHandler to declare a Web handler.
The handler will need an identifier, 'apiTokenChange'
, to disambiguate from multiple messages.
- Typescript
- JavaScript
Registering the Web Handler
Now we need to do something with the token. We will use WebjsContext.onShellMessage to register a listener function.
important
The first argument, 'apiTokenChange'
, must match the first argument of withWebHandler
.
Using the Feature
Finally, we need to pass the token from the shell to the Web script. We will use WebshellInvariantProps.webHandleRef to imperatively send messages:
- Typescript
- JavaScript
That's it. Our Web script is almost a React component now, it can react to prop changes!
tip
If your script relies on multiple props, it is best advised for performance and synchronization concerns to use one useEffect
with a payload comprising all props.
caution
Make sure to follow the rules of hooks and declare dependencies accordingly in useEffect
:
- Declaring no dependencies will cause the effect callback to execute on every render cycle, which might jam the native to Web communication bus;
- Missing dependencies could cause stale closures and inconsistencies.
Testing with Jest
Let's go back to our HandleLinkPressFeature
and implement a simple test.
Install Ersatz and Testing Library
To test injected scripts, the best way is to use
@formidable-webview/ersatz
.
It will mock the WebView and run a Web environment in jsdom.
- yarn
- npm
Implementing Tests
- Typescript
- JavaScript
Web-Engines Compatibility
For wide compatibility purposes of the Web script, it is recommended to:
- enforce ECMAScript 5 syntax with ESlint;
- lint the script with the eslint-plugin-compat to enforce backward compatibility with old engines;
See the tooling guide for more details on how to achieve this.