DEV Community 👩‍💻👨‍💻

Cover image for svelte-monetization - A minimal and lightweight wrapper for the Web Monetization API 💸
Robert
Robert

Posted on • Updated on

svelte-monetization - A minimal and lightweight wrapper for the Web Monetization API 💸

I've been wanting to learn Svelte and GitHub actions during this period of self-isolation and when I saw the Grant For The Web x DEV hackathon announcement, it sounded like the best time to check these items off my list.

My plan of learning Svelte and GitHub Actions is to create a plugin and publish it to NPM on every push using GitHub Actions.

What I built

I created a minimal and lightweight wrapper for the Web Monetization API in Svelte which will enable developers easily create reusable web monetized content. Thus, developers can concentrate on core application logic.

Submission Category:

Foundational Technology

Demo

Here's a demo app that uses svelte-monetization

Url: https://mediocre.now.sh

Link to Code

GitHub logo wobsoriano / svelte-monetization

A minimal and lightweight wrapper for the Web Monetization API

svelte-monetization

A minimal and lightweight wrapper for the Web Monetization API.

GitHub GitHub repo version GitHub last commit

Sample Project

$ cd example
$ npm install
$ npm run dev
Enter fullscreen mode Exit fullscreen mode

Installation

$ npm install --save svelte-monetization
Enter fullscreen mode Exit fullscreen mode

Usage

Add Svelte Monetization to your project

<script>
  import SvelteMonetization from "svelte-monetization";

  function handleProgress(event) {
    console.log(event.detail);
  }
</script>

<!-- Set up your payment pointer in your App.svelte -->
<svelte:head>
  <meta
    name="monetization"
    content="$coil.xrptipbot.com/701298d5-481d-40ff-9945-336671ab2c42" />
</svelte:head>

<SvelteMonetization
  let:isMonetized
  let:isLoading
  on:progress={handleProgress}>
  {#if isLoading}
    <div>Loading message here</div>
  {:else if isMonetized}
    <div>Monetized/premium content here</div>
  {:else}
    <div>Show ads here</div>
  {/if}
</SvelteMonetization>
Enter fullscreen mode Exit fullscreen mode

Events

You can also listen to Web Monetization browser events

How I built it

I cloned a good template for creating Svelte components that includes Rollup and Testing using svelte-testing-library + Jest.

Inside the src/Component.svelte file, where the magic happens, I've added the code below.

<script>
  import { onMount } from "svelte";
  let isLoading = true;
  let isMonetized = false;

  onMount(() => {
    if (!document.monetization) {
    // No web monetization polyfill is installed (e.g. Coil).
      isLoading = false;
      isMonetized = false;
      return;
    }

    // Check the value of document.monetization.state
    // to see if a user is web monetized.
    const { state } = document.monetization;

    if (state === "stopped") {
      // Not currently sending micropayments, nor trying to.
      isLoading = false;
      isMonetized = false;
    }

    // Determine when Web Monetization has started actively paying
    document.monetization.addEventListener("monetizationstart", event => {
      isLoading = false;
      isMonetized = true;
    });
  });
</script>

<slot {isLoading} {isMonetized} />
Enter fullscreen mode Exit fullscreen mode

With the code above, we can now use this component in our Svelte projects like below.

<script>
  import SvelteMonetization from "svelte-monetization";
</script>

<!-- Set up your payment pointer -->
<svelte:head>
  <meta
    name="monetization"
    content="$coil.xrptipbot.com/701298d5-481d-40ff-9945-336671ab2c42" />
</svelte:head>

<SvelteMonetization let:isLoading let:isMonetized>
  {#if isLoading}
    <div>Loading message here</div>
  {:else if isMonetized}
    <div>Monetized/premium content here</div>
  {:else}
    <div>Show ads here</div>
  {/if}
</SvelteMonetization>
Enter fullscreen mode Exit fullscreen mode

There are two things that I want to notice:

  1. The <svelte:head> element. This allows us to insert elements inside the <head> of our document.

  2. The let directive in the SvelteMonetization component. We use this to expose our isLoading and isMonetized states from the Component.svelte so that we can use it to conditionally render some markup.

Easy peasy, right? How about Web Monetization browser events? Should we implement our own?

Thanks to Svelte component events, we can refactor our code to dispatch browser events from the monetization API.

<script>
  import { onMount, createEventDispatcher } from "svelte";

  // createEventDispatcher must be called when the component is first instantiated
  const dispatch = createEventDispatcher();

  let isLoading = true;
  let isMonetized = false;

  onMount(() => {
    if (!document.monetization) {
      isLoading = false;
      isMonetized = false;
      return;
    }

    const { state } = document.monetization;

    if (state === "stopped") {
      isLoading = false;
      isMonetized = false;
    }

    // Since monetization events always start with the monetization word,
    // we can just loop over the event names to make our code shorter.
    const events = ["start", "progress", "pending", "stop"];
    events.forEach(name => {
      document.monetization.addEventListener("monetization" + name, event => {
        dispatch(name, event.detail);

        if (name === "start") {
          isLoading = false;
          isMonetized = true;
        }
      });
    });
  });
</script>
Enter fullscreen mode Exit fullscreen mode

How can we listen to events in our SvelteMonetization element? Just add an on directive plus the name of the event.

<script>
  import { onMount } from "svelte";
  import SvelteMonetization from "svelte-monetization";

  function handleProgress(event) {
    // you can use this to save micropayments
    // to your own database
    console.log("progress", event.detail);
  }
</script>

<SvelteMonetization
  let:isLoading
  let:isMonetized
  on:progress={handleProgress}>
  {#if isLoading}
    <div>Loading message here</div>
  {:else if isMonetized}
    <div>Monetized/premium content here</div>
  {:else}
    <div>Show ads here</div>
  {/if}
</SvelteMonetization>
Enter fullscreen mode Exit fullscreen mode

Deployment

Next, we want to automatically publish a new version of our package when creating a new release on GitHub. So it's now a good time to learn and use GitHub Actions.

Here's the action:

name: Publish

on:
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12
          registry-url: https://registry.npmjs.org/
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
Enter fullscreen mode Exit fullscreen mode

This code is the GitHub Action I used, let's see what it does.

name: Publish
Enter fullscreen mode Exit fullscreen mode

First we put a name to the action, this will be displayed in the checks of each push.

on:
  push:
    branches: [ master ]
Enter fullscreen mode Exit fullscreen mode

Then we configure when we want the action to run, in this case I'm saying on each push event we want it to publish to npm.

jobs:
  build:
    runs-on: ubuntu-latest
Enter fullscreen mode Exit fullscreen mode

Then we create our job build and configure it to run on the latest version of Ubuntu.

    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 12
          registry-url: https://registry.npmjs.org/
      - run: npm ci
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
Enter fullscreen mode Exit fullscreen mode

Now we need to configure the steps of the job, this is what it does:

  • Get access to the repository files.
  • Install Node.js, with the version 12 and using the registry URL of npm, this could be changed to a custom registry or the GitHub registry.
  • Run the npm ci command to install the package dependencies.
  • Run the npm publish command, this command is also run with the environment variable NODE_AUTH_TOKEN whose value is a secret configured in the repository called NPM_AUTH_TOKEN.

Finally, we can install the component in our Svelte applications by running

npm install --save svelte-monetization
Enter fullscreen mode Exit fullscreen mode

Additional Resources/Info

svelte-monetization is now listed in the official Svelte code showcase.

If you are integrating Web Monetization with a Vue 3 app, you can check my dev post for some inspiration.

Up next

In the next post, I will create a sample application that uses our svelte-monetization component.

Top comments (5)

Collapse
 
cyberdees profile image
☞ Desigan Chinniah ☜

That was quick. Nice work, Robert!

Collapse
 
wobsoriano profile image
Robert Author

Thank you. Part 2 coming up!

Collapse
 
wobsoriano profile image
Robert Author • Edited on

[POST UPDATED]

Before, svelte-monetization uses named props to render monetized content:

{#if isLoading}
  <slot name="loading" />
{:else if isMonetized}
  <slot name="monetized" />
{:else}
  <slot name="not-monetized" />
{/if}

Now, while exploring Svelte docs, I've learned that you can pass values back to the parent using props too! So I refactored it.

<slot {isLoading} {isMonetized} />

Now it's much cleaner and can be used like this.

<SvelteMonetization let:isMonetized let:isLoading>
  {#if isLoading}
    <div>Loading message here</div>
  {:else if isMonetized}
    <div>Monetized/premium content here</div>
  {:else}
    <div>Show ads here</div>
  {/if}
</SvelteMonetization>
Collapse
 
brunner1000 profile image
Brunner

congrats. The post was interesting to read.

Collapse
 
wobsoriano profile image
Robert Author

Thanks. I'll be adding sample apps that uses this wrapper.

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs