Skip to content

Solid Microstore - Technical

This information is meant for developers looking at the code of the Microstore application.

Stack

Framework

The application is written in Angular 13.x.x and thus in TypeScript. It is written as a browser application.

Package manager

The yarn package manager was used. It behaves very similar to npm but is a bit faster and more modern in use.

To install all libraries and build the code: run yarn from the project folder /solid-microstore.

To run the code in a development server: execute yarn start from the project folder /solid-microstore.

Libraries

The noteworthy libraries used and the reason for doing so are listed below:

library reason npm link
n3 Implementation of the RDF.js low-level specification that lets you handle RDF. https://www.npmjs.com/package/n3
@inrupt/solid-client-authn-browser Authenticate web apps (in the browser) with Solid identity servers. https://www.npmjs.com/package/@inrupt/solid-client-authn-browser
@inrupt/vocab-common-rdf Bundle of common RDF vocabularies. https://www.npmjs.com/package/@inrupt/vocab-common-rdf
sockjs-client Counterpart of SockJS used on the server. it provides a WebSocket-like object. https://www.npmjs.com/package/sockjs-client
solid-wmp-client Handles communication with a server component implementing the proposed WMP specification https://www.npmjs.com/package/solid-wmp-client
web-monetization-polyfill Polyfill the Web Monetization JavaScript API on document.monetization, required until it is provided by browsers https://www.npmjs.com/package/web-monetization-polyfill

Code notes

Pages

Angular works with components. Each subpage is a Component.

Important for this demonstrator is that where possible, the standard Web Monetization Javascript API was used. Typically this amounts to registering event listeners to the document.monetization object and reacting to the fired events.

Anything else noteworthy will be explained per page.

About page

/src/app/about

Nothing special happens on this page.

Paywall page

/src/app/paywall

constructor(...)

On creation of this page, eventlisteners are added for all four MonetizationEvents, to not miss any events that are fired early on. The listeners manage the display of the paywall overlay.

constructor(...) {
    // Setup listeners
    document.monetization?.addEventListener('monetizationstart', evt => this.onStart(evt));
    document.monetization?.addEventListener('monetizationprogress', evt => this.onProgress(evt));
    document.monetization?.addEventListener('monetizationstop', evt => this.onStop(evt));
    document.monetization?.addEventListener('monetizationpending', evt => this.onPending(evt));
}
unlock()

The page starts with a locked paywall overlay that can be unlocked only when the user is logged in. For that the loggedIn property of the Auth service is checked. If that unlock button is clicked, the unlock() method is called.

This will first call the WMP service to check whether monetization is supported. If so, the Solid service is called to fetch the stored WMP form the user's WebID. The URL of the WMP is then passed on to the WMP service to setup the payment.

ngOnDestroy()

There is an ngOnDestroy() method, this lifecycle hook gets called by the angular framework when the component is destroyed. In our case, since it is a full page, this happens when the user routes to another page. The code here calls the WMP service to close the current (if any) monetization stream.

Mixed content page

/src/app/mix

constructor(...)

On creation of this page, eventlisteners are added for two MonetizationEvents, to not miss any events that are fired early on. The listeners manage the display of the LOCKED labels.

constructor(...) {
// Setup listeners
      document.monetization?.addEventListener('monetizationstart', _ => this.locked = false);
      document.monetization?.addEventListener('monetizationstop', _ => this.locked = true);
}
ngOnInit()

This lifecycle hook is called upon initialization of the page by the angular framework. Here two things happen:

  1. The Image service is being called to generate 100 random images.
  2. We subscribe to the statusChanged$ Subject property of the Auth service to do the following once the user is logged in:

ngOnDestroy()

There is an ngOnDestroy() method, this lifecycle hook gets called by the angular framework when the component is destroyed. In our case, since it is a full page, this happens when the user routes to another page. The code here calls the WMP service to close the current (if any) monetization stream.

Other components

The leftover components are used as custom HTML Elements in the HTML. A component has HTML, CSS and code (typescript) associated with it. You can use a component in HTML by using an HTML tag to refer to it.

<!-- Example tag -->
<app-login-btn [provider]="p"></app-login-btn>

/src/app/components/navigation

This component just encapsulates the navigation bar on top. It provides a login butten and offloads the login() and logout() actions to the Auth service.

Micropayment counter

/src/app/components/counter

This component encapsulates the logic to view the progress and status of the micropayments.

constructor()

On creation of this component, eventlisteners are added for all four MonetizationEvents, to not miss any events that are fired early on. The listeners manage the update of the counter view.

constructor() {
    // Setup listeners
    document.monetization?.addEventListener('monetizationstart', e => this.onStart(e));
    document.monetization?.addEventListener('monetizationprogress', e => this.onProgress(e));
    document.monetization?.addEventListener('monetizationstop', e => this.onStop(e));
    document.monetization?.addEventListener('monetizationpending', e => this.onPending(e));
}

The code itself is well enough documented to figure out how this counter works.

Services

Services are singleton instances that are dependency injected into the components that list them as argument of their constructors.

Auth service

/src/app/services/auth.service.ts

This service is mainly a wrapper around the @inrupt/solid-client-authn-browser library.

handleIncomingCallback()

This method calls two functions from the auth library that should be called when the browser is redirected to the application's callback URI. This method is called from inside the app.component.ts ngOnInit() hook, which is one of the first hook to be executed when the application loads.

login()

This calls the auth library's login() method but with a fixed value of http://solidcommunity.net.

fetch property

This returns the custom fetch() function of the auth library. Their security architecture does not allow for the access token to be exposed, that's why a custom fetch function is exposed that fills in the required Authorization and DPoP headers for you.

Image service

/src/app/services/image.service.ts

This is a simple service that is able to consistently generate multiple generated images with randomly picked colors, interleaved with some premium images.

Solid service

/src/app/services/solid.service.ts

This service contains the code to read from the WebID document on the solid pod of the user. It mainly uses the n3 library to read the Solid WebID profile and fetch the proper triples.

The code itself is documented.

WMP service

/src/app/services/wmp.service.ts

This service encapsulates the solid-wmp-client library.

constructor(...)

The constructor instantiates a WmpClient instance, assigns a variable wmHandler to the MonetizationHandler helper object and assigns a variable fetch to the custom fetch function from the Auth service.

constructor(auth: AuthService) {
    this.wmp = new WmpClient();
    this.wmHandler = this.wmp.getMonetizationHandler();    
    this.fetch = auth.fetch;
}
setupWMPayment(...)

This method instructs the wmp client instance to setup the payment from the WMP on the given wmpUrl using the given fetch function.

Info

On the API docs of the solid-wmp-client library you can read more about how this works.

closeMonetizationStream()

This methode will instruct the wmp client instance to close any open payment stream.

App routing

All app routing paths can be found in /src/app/app-routing.modules.ts

WebID Profile

The WebID profile is stored in RDF. That means that data read from that profile should be queried as RDF. To query a WebID Profile document for a WMP url, this tuple is being searched.

https://[my-username].solidcommunity.net/profile/card#me
@prefix : <#>.
...
@prefix ns0: <https://webmonetization.org/ns#>.

...

#me 
    ...
    ns0:hasProvider :me-webmonetization-provider.

:me-webmonetization-provider
    a ns0:Provider;
    ns0:apiUrl "http://wmp.localhost".

The actual query is performed by the Solid service library.