Reflex-Based Web Monetization API
Tags: gftwhackathon, webnative, scopedjs, chtml
Published: 2020-06-06 04:04:11

This is a Reflex-based (live) object that wraps the Web Monetization API. [Reflex](https://docs.web-native.dev/reflex) is JavaScript's new observability API that lets us observe JavaScript objects and arrays, combining the magic of the once useful `Object.observe()` API with other JavaScript reflection APIs - `Object` and `Reflect`. Making the Web Monetization API a live, observable object will be opening up new exciting ways to integrate Web Monetization.

## Problem Solved
State changes in the Web Monetization API is event-based and this leaves us much engineering work to do. For example, to track changes to the `document.monetization.state` property, we have to remember three (3) different event names ( `monetizationpending`, `monetizationstart`, `monetizationstop`) and implement three (3) different callbacks. A Reflex-based object would simply offer this property as an observable property.

```js
// The initial state...
console.log(document.monetization.state);

// Subsequent states would now be...
Reflex.observe(document.monetization, 'state', newState => {
    console.log(newState); // pending, started, stopped
});
```

This is the level of simplicity led by Reflex, underpinning a growing collection of Reflex-based APIs at the [Web-Native Project](https://web-native.dev). The long-term promise of this is *reactivity* without a framework; change-detection in vanilla JavaScript!

The Web Monetization API will now also be *reflexive* and natively playing with the rest of the Reflex-driven world.


## Usage
This library is part of the official [Reflex Components](https://docs.web-native.dev/reflex-components) package.

```js
import {Money} from '@web-native-js/reflex-components';
import Reflex from '@web-native-js/reflex';

const WebMonetization = Money.WebMonetization;
// Initialize an instance with a payment pointer
let monetization = WebMonetization.init('$ilp.example.com/me');

// Observe state
Reflex.observe(monetization, 'state', newState => {
    // Code here...
});

// Start/stop anytime
monetization.start();
// monetization.stop();
```

### Features
+ **Start/Stop Methods** - Calling the `.start()` method would create the appropriate meta tag in the document head, if not already exists. The `.stop()` method removes it.
+ **Multiple Pointer Implementations** - Multiple instances could be initialized with different payment pointers. Calling `.start()` on an instance starts a new payment stream and stops any currently active one.
+ **Optional Prompt** - Users coming to a monetized site without a supporting browser might need to be informed of the feature. This can be specified in our call to `.init()`.
    ```js
    let monetization = WebMonetization.init('$ilp.example.com/me', {prompt: true});
    ```
    The prompt automatically detects the user's browser and asks to take them to the appropriate extension for their browser.
+ **Observable Properties** - Now we only need to think of properties, not events and callbacks. These properties are as follows:
    + **state** - `(string)`. This can either be `started`, `pending`, `stopped`. The following three other properties also reflect the state of the instance.
    + **pending** - `(object|boolean)`. This becomes an object when *state* goes `pending`, `false` otherwise. This object maps to the `event.detail` property of the underlying `monetizationpending` event.
    + **started** - `(object|boolean)`. This becomes an object when *state* turns  `started`, `false` otherwise. This object maps to the `event.detail` property of the underlying `monetizationstart` event.
    + **stopped** - `(object|boolean)`. This becomes an object when *state* gets `stopped`, `false` otherwise. This object maps to the `event.detail` property of the underlying `monetizationstopped` event.
    + **progress** - `(object)`. This property reflects the `event.detail` property of the underlying `monetizationprogress` event. Each progress update also automatically updates the following other properties.
    + **currentTotal** - `(object)`. This object represents the totals between starts and stops on the instance. It begins at zero on each call to `.start()`. The `currentTotal.amount` property gives the raw sum of micro payment sent during the stream, while `currentTotal.value` gives the sum in the format of the active currency.
    + **sessionTotal** - `(object)`. This object represents the overall totals on the instance. The `sessionTotal.amount` property gives the raw sum, while `sessionTotal.value` gives the sum in the format of the active currency.
    + **currency** - `(string)`. The payment currency.

## Usage in ScopedJS - the Fun Part!
Using [ScopedJS](https://docs.web-native.dev/chtml/specs), the Reflex-based, micro runtime for JavaScript, We could **declaratively** create behaviour over a `WebMonetization` instance right within an HTML markup.

First we would make the `WebMonetization` instance available to *scoped scripts*.

```js
let ENV = window.WebNative.ScopedJS.ENV;
ENV.globals.monetization = WebMonetization.init('$ilp.example.com/me');
```

Now we would be able to reference the `monetization` object from any scoped script and build behaviour on the fly with any element.

Below we are toggling the visibility of an exclusive section on the page by simply referencing the `monetization.state` property.

```html
<body>
    <div id="exclusive">
        This is exclusive content!
        <script type="text/scoped-js">
          // This statement will run as a reflex action
          // to each change in monetization.state
          this.classList.toggle('hidden', monetization.state !== 'started');
        </script>
    </div>
</body>
```

And we can display a live total; and even implement a threshold for our exclusive content. (E.g to show more exclusive content as payment adds up.)

```html
<body>
    <div id="exclusive">
        This exclusive content!
        ...
        And you've streamed us <span id="total"></span> in total.
        <script type="text/scoped-js">
          let elTotal = this.querySelector('#total');
          elTotal.innerHTML = monetization.sessionTotal.value + ' ' + monetization.sessionTotal.currency;
          this.classList.toggle('hidden', monetization.sessionTotal.amount < 10);
        </script>
    </div>
</body>
```

We could also add a *start button* to the page.

```html
<body>
    <div id="start">
        Show exclusive content
        <script type="text/scoped-js">
          this.addEventListener('click', () => {
              monetization.start();
          });
        </script>
    </div>
</body>
```
## Working Demo
The [web-native.dev](https://web-native.dev)'s project support banner featuring Web Monetization was implemented using the Reflex-based Web Monetization API.

## What's Next for this Project
+ Good documentation
+ Bug fixes
+ More examples
+ More experiments