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:
- The Image service is being called to generate 100 random images.
-
We subscribe to the
statusChanged$
Subject property of the Auth service to do the following once the user is logged in:- Request the Solid service to fetch the WMP URL from the WebID Profile document.
- Call the WMP service to set up the payment using that URL.
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>
Navigation component¶
/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.
@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.