This is a rough idea on how a standardized and/or built-in browser extension may support web monetization.
- Browser sees the monetization tag and fetches data at payment pointer (
spspResponse
). If fetch/parse failed, terminate. - If the user has disallowed the site not to be monetized, terminate. (Don't terminate before fetching as the website can use it to annoy the user - "please add us to allow-list" etc.)
- Browser sends events to the extension, and forwards the replies to the website or user, as explained in approaches below.
- As the browser fetches the
spspResponse
as well as intermediates between the extension and website, the extension need not access the full content of all pages and can demonstrate a better respect for user's privacy. - Extension may still ask for access additional permissions. This could be a distinguishing feature of extensions.
- All approaches below allow for multiple payment streams.
- The UI (out of scope of the project) may have following user controls: add to blocklist/allowlist, tip (pay more)/demote (pay less). It might also be useful to have a single UI for all monetization extensions, which will act as the communication channel between the user and the active monetization extension through the browser.
The extension plays the role of WM agent (decides how much to pay) as well as payment manager (makes the payment).
- Approach 1: Short polling. Browser queries the status of payments made until now.
- Approach 2: Long polling. Extension blocks browser's requests until it pays.
- Approach 3: Replace polling in approach 1 with push events (something like
browser.monetization.dispatchEvent()
)
// @file background.js
browser.monetization.addEventListener(
'start' | 'stop' | 'pause' | 'resume' | 'poll'
);
// actual API will be `browser.monetization.onStart.addListener(args)`
start(sessionId, spspResponse, context)
:- decide whether and how much to pay.
context
^1 contains hints for this decision. - authenticate, set up streams.
respondWith(resolve(amountToBePaid) || reject(reason))
- decide whether and how much to pay.
stop(sessionId)
:- end stream.
respondWith(amountPaid)
poll(sessionId)
:- responds with current payment status.
- this corresponds to monetizationProgress on the website.
respondWith(resolve(amountPaidYetInThisSession) || reject(reason))
pause
andresume
events are optimizations to avoid unnecessarily regenerating streams/connections.
pause(sessionId)
:
- tab went out of focus maybe. keep streams alive, but do not pay.
- stop if not resumed after sometime.
respondWith(true || false)
resume(sessionId)
:
- tab is back in focus. start paying again.
- if stopped already,
reject(reason)
.respondWith(resolve(sessionId) || reject(reason))
Payment Streaming:
- Browser issues "start" event and extension starts payments in background, after responding to the event.
- Browser issues "poll" events occasionally for payment status and extension immediately responds with current status. Browser uses this info to inform the website.
Tipping/demoting:
stop(sessionId)
start(newSessionId, cachedSpspResponse, newContextWithDifferentWeight)
Token refresh:
If poll(sessionId)
is rejected due to "token expired":
stop(sessionId)
start(newSessionId, newSpspResponse, context)
(similar to PaymentRequest API)
browser.monetization.addEventListener('setup' | 'request');
Payment streaming:
- Browser issues the
setup(sessionId, spspResponse, context)
event. The extension makes a decision on payment and responds with the amount to be paid. It also sets up the connections/streams. If extension rejects, terminate. - Browser issues
request(sessionId, remainingAmount)
events until rejected (or until remainingAmount reaches 0). Each event is blocked by the extension until it deems necessary. The event is responded with amountPaid. In subsequent calls, browser subtracts from remainingAmount.
Stop:
- Browser stops issuing "request" events.
- Extension may clear up streams that haven't been used recently.
Token Refresh:
- "request" is rejected with appropriate reason.
- Browser restarts process.
Tipping/demoting:
- Browser creates a new session and issues a "setup" event with a new context having different weight than before.
- Same as starting.
- Extension may clear up streams that haven't been used recently.
When monetizationprogress
event is fired?
- Approach 1: Browser manages when to issue
monetizationprogress
to websites by issuingpoll
events. - Approach 2: Extension manages when browser issues
monetizationprogress
to the website, by blocking response torequest
events. - Approach 3: Extension decides when it's appropriate to inform the browser/website.
monetization.addEventListener('monetizationprogress', event => {
// This event can be raced-out (as it may never fire).
if (event.error) {
handleError();
} else {
verifyPayment(event.receipt).then(() => showExclusiveContent());
}
});
^1: context may contain details like total amount paid earlier, amount paid in this session, weight (tipped or punished), current website, visit count etc.