Skip to content

Instantly share code, notes, and snippets.

@sidvishnoi
Last active January 28, 2021 07:18
Show Gist options
  • Save sidvishnoi/270b9b0e58e8d7df109507cdc2d069d3 to your computer and use it in GitHub Desktop.
Save sidvishnoi/270b9b0e58e8d7df109507cdc2d069d3 to your computer and use it in GitHub Desktop.
Web Monetization through a standardized browser extension

Proposal: Web Monetization through a standardized browser extension

This is a rough idea on how a standardized and/or built-in browser extension may support web monetization.

Flow

  1. Browser sees the monetization tag and fetches data at payment pointer (spspResponse). If fetch/parse failed, terminate.
  2. 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.)
  3. Browser sends events to the extension, and forwards the replies to the website or user, as explained in approaches below.

Notes

  1. 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.
  2. Extension may still ask for access additional permissions. This could be a distinguishing feature of extensions.
  3. All approaches below allow for multiple payment streams.
  4. 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.

Browser Extension API

The extension plays the role of WM agent (decides how much to pay) as well as payment manager (makes the payment).

  1. Approach 1: Short polling. Browser queries the status of payments made until now.
  2. Approach 2: Long polling. Extension blocks browser's requests until it pays.
  3. Approach 3: Replace polling in approach 1 with push events (something like browser.monetization.dispatchEvent())

Approach 1

// @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))
  • 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 and resume 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:

  1. Browser issues "start" event and extension starts payments in background, after responding to the event.
  2. 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:

  1. stop(sessionId)
  2. start(newSessionId, cachedSpspResponse, newContextWithDifferentWeight)

Token refresh:

If poll(sessionId) is rejected due to "token expired":

  1. stop(sessionId)
  2. start(newSessionId, newSpspResponse, context)

Approach 2:

(similar to PaymentRequest API)

browser.monetization.addEventListener('setup' | 'request');

Payment streaming:

  1. 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.
  2. 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:

  1. Browser stops issuing "request" events.
  2. Extension may clear up streams that haven't been used recently.

Token Refresh:

  1. "request" is rejected with appropriate reason.
  2. Browser restarts process.

Tipping/demoting:

  1. Browser creates a new session and issues a "setup" event with a new context having different weight than before.
  2. Same as starting.
  3. Extension may clear up streams that haven't been used recently.

API for website:

When monetizationprogress event is fired?

  1. Approach 1: Browser manages when to issue monetizationprogress to websites by issuing poll events.
  2. Approach 2: Extension manages when browser issues monetizationprogress to the website, by blocking response to request events.
  3. 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.

/**
* @file background.js
*/
browser.monetization.onStart.addListener((sessionId, spspResponse, context) => {
const { destinationAccount, sharedSecret, receiptsEnabled } = spspResponse;
/** setup connections/streams for `sessionId` to send payments to `spspResponse`,
* make payment decision using `context` and other extension APIs. */
// inform the browser (and website) when a payment is successful for a given sessionId
// can be called multiple times in future once a onStart is fired. (this is "notifyProgress")
const amount = { value, assetScale, assetCode };
browser.monetization.completePayment(sessionId, amount, optionalReceipt);
});
browser.monetization.onStop.addListener(sessionId => {
/** browser has requested to stop payments for given sessionId. cleanup. */
});
browser.monetization.onPause.addListener(sessionId => {
/** browser has requested to pause payments for given sessionId for a while
* (user has switched tab, or is inactive on current page etc.).
* keep connections alive, but do not make payments. */
});
browser.monetization.onResume.addListener(sessionId => {
/** user is back to a tab/active again.
* resume payments, try to reuse previous stream if possible. */
});
/** Some time later while paying, the spspResponse/tokens may expire. Request a new session with: */
const newSessionId = await browser.monetization.refresh(oldSessionId);
// Throws if oldSessionId is invalid.
// Returns null if failed to fetch spspResponse or website removed monetization tags.
// Results in:
// - onStop being fired for oldSessionId
// - optionally, onStart being called with newSessionId and new spspResponse.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment