# 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

```javascript

// 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
<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.

```javascript

/* 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.

```javascript
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

```javascript
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.

```javascript
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

#### Navigation

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

```javascript
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.

```javascript
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.

```typescript
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.

```typescript

const { screenHook } = JS2Native();

  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.

```typescript

const { sessionStorage } = JS2Native();

  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>;
}
```

#### Navigation

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.

```typescript

const { navigation } = JS2Native();

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

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