Skip to main content

JS to Native Bridge

The JS2Native api allows to leverage some of the native app's APIs when building web content running inside a webview. this page explains how to use the @applicaster/quick-brick-js-2-native package from the Quick Brick framework, to build web content using this bridge

Using JS 2 native

Requirements

  • iOS SDK 4+
  • Android SDK 5+
  • QB ^5.1.0
  • @applicaster/quick-brick-js-2-native
  • Webview screen plugin ^3.0.0
  • Webview component plugin ^2.0.0

Installing this library

Simply install the package via npm or yarn:

$ yarn add @applicaster/quick-brick-js-2-native

or

$ npm install @applicaster/quick-brick-js-2-native --save

you can then import the library using import or require syntax

import JS2Native from "@applicaster/quick-brick-js-2-native";

// or

var JS2Native = require("@applicaster/quick-brick-js-2-native");

Usage through CDN

You can access a UMD version of this library at this url: https://unpkg.com/@applicaster/quick-brick-js-2-native@<version>/lib/bundle.js

Replace <version> with the version you want to use (can be x.y.z, next, latest, etc, all valid npm version or tag)

Add a script tag in your HTML document, this will load the library and expose the bridge in window.Applicaster.JS2Native

<html>
<head>
<script
lang="javascript"
src="https://unpkg.com/@applicaster/quick-brick-js-2-native@5.0.1-rc.24/lib/bundle.js"
></script>
<script lang="javascript">

window
.Applicaster
.JS2Native
.sessionStorage
.getItem("app_name")
.then(function (appName) {
// returned data from native App
});
</script>
</head>
<body>
<!-- .... -->
</body>
</html>

Debugging

In your web content, you can debug using console.log, console.warn and console.error. These functions are polyfilled in the JS2Native bridge so that log output from your web content will be forwarded to the React Native debugger. the original call to the console module is preserved, so if you prefer to debug your web content in the webview using features like chrome://inspect you can still do it.

The polyfill doesn't support console.info console.debug or the console.group API.

While running the @applicaster/quick-brick-js-2-native library in the browser will provide you with mocks to get a sense of how your web content will interact with the app, it is useful at some point to be able to debug your app content inside the actual app itself.

In order to do this, set up your app with your webview screen where you want to see it (as a hook, a main navigation tab...). In this screen, set a custom URL in the screen's configuration to your local environment where your web content runs:

  • on iOS, you will need to replace localhost by the IP of the machine hosting your web content, i.e. http://192.168.10.10:3000
  • on Android, you will need to proxy the port through adb calling adb reverse tcp:3000 tcp:3000 if your web content is served on port 3000

Build your app if needed, then download it on your device. The app will load the web content from your local server. From there, if your device is tethered to the machine serving your web content, you will be able to debug the web content:

  • with Chrome for Android apps, going to chrome://inspect/#devices
  • with Safari for iOS apps, looking for the web content in the Develop menu

API

Once the module is installed in your project, you can import it in any file and start to interact with the bridge.

import JS2Native from "@applicaster/quick-brick-js-2-native";

/* this allows you to access the JS2Native API.
If the web content is not running in an applicaster webview,
this is still safe to use, the library will return you a mock */
const bridge = JS2Native();
// start using the bridge

In order to allow native to JS communication, the features of the bridge are attached to the window object under window.Applicaster.JS2Native. For convenience, the JS2Native function exported from the @applicaster/quick-brick-js-2-native package will allow you to access the bridge module directly, and provide you with mocked features when running in a browser, where the bridge features wouldn't be declared. If you directly access the bridge using window.Applicaster.JS2Native the bridge feature won't work properly unless you are running the web content in a webview of your app.

Screen Hooks

The bridge allows you to intregrate with our hooks API.

const { screenHook } = JS2Native();
properties
  • screenHook.data:
    • screenHook.data.payload: contains the payload which triggered the hook execution. It is usually the feed entry on the cell which was pressed before the hook was opened.
    • screenHook.data.plugin: contains information about the screen hook plugin
methods
  • screenHook.callback: (object) => void: allows you to notify the app that the execution of the hook is done, and specify what the outcome is. This function takes an object as argument with several properties:
    • object.success: boolean: flag which indicates if the hook execution was succesfull or not
    • object.payload: Object: if you need to mutate the payload - i.e. add a signed token to a stream url, add extensions properties, etc - you can assign the data in this property
    • object.error: Error: if the hook execution had an error, you can pass an Error object providing the reason of the failure.

Session Storage

The bridge allows you to integrate with our Session storage API. The functions below are essentially proxies to the underlying storage API

const { sessionStorage } = JS2Native();

The sessionStorage functions return promises, and can be used either with a .then callback, or the async / await syntax.

methods
  • sessionStorage.getItem: (key: string, namespace?: string) => Promise<any>: Retrieves the value for a specific key and optional namespace from session storage.
  • sessionStorage.setItem: (key: string, value: any, namespace?: string) => Promise<boolean>: Sets a value for a specific key and optional namespace in session storage. Returns a promise with a boolean indicating wether the action was succesful or not.

Local Storage

The bridge allows you to integrate with our Local storage (persistent) API. The functions below are essentially proxies to the underlying storage API.

const { localStorage } = JS2Native();

The localStorage functions return promises, and can be used either with a .then callback, or the async / await syntax.

methods
  • localStorage.getItem: (key: string, namespace?: string) => Promise<any>: Retrieves the value for a specific key and optional namespace from local storage.
  • localStorage.setItem: (key: string, value: any, namespace?: string) => Promise<boolean>: Sets a value for a specific key and optional namespace in local storage. Returns a promise with a boolean indicating wether the action was succesful or not.
  • localStorage.getSecuredItem: (key: string, namespace?: string) => Promise<any>: Retrieves the value for a specific key and optional namespace from local storage. This is secure storage leveraging the native keychain / secure capability
  • localStorage.setSecuredItem: (key: string, value: any, namespace?: string) => Promise<boolean>: Sets a value for a specific key and optional namespace in local storage. Returns a promise with a boolean indicating wether the action was succesful or not. This is secure storage leveraging the native keychain / secure capability
nativeEnvironment

This flag lets you know wether you are currently running in a webview inside the app, or if you are relying on the mock provided for development in the browser. If this value is set to true, you are effectively in the app's native environment. If not, you are running in the browser

The JS2Native bridge allows to interact with the app's navigation stack. This allows you to request the current webview to be closed, returning to the previous screen, or trigger navigation to any screen. To that end, the navigation bridge exposes the list of available screens in the app

const { navigation } = JS2Native();
methods
  • closeWebview: () => void: requests to close the webview. Will go back to the previous screen.
  • navigateToScreen: (screenId: string) => void: requests to navigate to a specific screen. The screenId is the id of the screen you want to navigate to, and can be resolved from the array of screen provided in the bridge.
  • screens: Array<ZappScreen>: Array of screens available inside the app. You will need the id of one of these screens to to trigger a navigation action to this screen.

Native Share

The JS2Native bridge allows to open app's native share component. It will use 2 fields:

  • Share url: item.extensions.share_url or item.link.href.
  • Message: item.extensions.share_message or Share message string from share plugin configuration options.
const { nativeShare } = JS2Native();
methods
  • nativeShare: (item: ZappEntry | ZappFeed) => void: open share component with provided item.

Typescript

Here is the type declaration of the bridge module returned by the JS2Native function. You can see all data and functions available in the bridge. See the example section below for more detailed information on how to use the session storage and hook screen capabilities of the bridge.

type ApplicasterJS2NativeBridge = {
sessionStorage: {
getItem: (key: string, namespace?: string) => Promise<any>;
setItem: (key: string, value: any, namespace?: string) => Promise<boolean>;
};
localStorage: {
getItem: (key: string, namespace?: string) => Promise<any>;
setItem: (key: string, value: any, namespace?: string) => Promise<boolean>;
getSecuredItem: (key: string, namespace?: string) => Promise<any>;
setSecuredItem: (key: string, value: any, namespace?: string) => Promise<boolean>;
};
screenHook: {
data: {
payload: ZappFeedEntry;
plugin: ZappPlugin;
};
callback: (arg: ({ success: boolean, payload: ZappFeedEntry, error?: Error )) => void;
};
nativeEnvironment: boolean;
nativeShare: (item: ZappEntry | ZappFeed) => void;
};

// overloading the window object to have valid types when accessing window.Applicaster.JS2Native object
declare global {
interface Window {
Applicaster: { JS2Native: ApplicasterJS2NativeBridge };
}
}

Examples

The example below are written in React, but the bridge can be used with any framework.

Hooks

When the webview presenting your web content is configured as a screen hook, the hooks API can give you access to the hook payload, the plugin configuration, and the hook callbacks required to tell the app what is the outcome of the hook.

the callback function behaves exactly like the hooks callback in our React Native API. It allows you to return a success status, modify the hook payload, and optionnaly pass an error.

import React, { useEffect } from "react";
import JS2Native from "@applicaster/quick-brick-js-2-native";

const { screenHook } = JS2Native();

export function MyComponent(props) {
useEffect(() => {
console.log("hook data", screenHook.data.payload);
console.log("hook plugin", screenHook.data.plugin);
}, []);

const onPress = () => {
screenHook.callback({
success: true,
payload: {
...screenHook.data.payload,
content: {
src: `${screenHook.data.payload.content.src}?token=${token}`,
},
},
});
};

const onCancel = () => {
screenHook.callback({
success: false,
});
};

const onError = () => {
screenHook.callback({
success: false,
error: new Error("oups !"),
});
};

return (
<div>
<button onClick={onSuccess}>success</button>
<button onClick={onCancel}>cancel</button>
<button onClick={onError}>error</button>
</div>
);
}

Session Storage

The JS2Native api allows you to interact with the native app's Session Storage capabilities. You can get and set values in session storage.

When running the web content outside of a Quick Brick webview screen, the @applicaster/quick-brick-js-2-native library will expose a mock mimicking interaction with the native storage, and relying on the web session storage API.

import React, { useEffect } from "react";
import JS2Native from "@applicaster/quick-brick-js-2-native";

const { sessionStorage } = JS2Native();

export function MyComponent(props) {
const signIn = async () => {
const appName = await sessionStorage.getItem("app_name");

try {
const result = await sessionStorage.setItem(
"token", // key
"xxxxx.yyyyy.zzzzz", // value
"my_namespace" // namespace
);

if (!result) {
throw new Error("couldn't set token");
}
} catch (e) {
// handle errors
}
};

useEffect(() => {
signIn();
}, []);

return <div>{/* ... */}</div>;
}

In this example, we'll use the bridge's navigation capabilities to show two buttons, one closing the webview screen, and one navigating to the home screen.

import React, { useEffect } from "react";
import JS2Native from "@applicaster/quick-brick-js-2-native";

const { navigation } = JS2Native();

const homeScreen = navigation.screens.find((screen) => screen.home);

export function MyComponent() {
return (
<>
<Button title="close webview" onPress={() => navigation.closeWebview()} />
<Button
title="go to home"
onPress={() => navigation.navigateToScreen(homeScreen.id)}
/>
</>
);
}