Skip to content

Commit

Permalink
fix(content): dispatch MonetizationEvent instead of CustomEvent (#346)
Browse files Browse the repository at this point in the history
1. Dispatch `MonetizationEvent` instead of `CustomEvent` with detail
2. Keep supporting `event.detail` access for backward compat, but add a warning on accessing for first time.
3. Rename internal event (for communication between content scripts) from `monetization-v2` to `__wm_ext_monetization`, along with `onmonetization-attribute-changed` to `__wm_ext_onmonetization_attr_change`.
4. Rename contentScript to polyfill
5. Turn contentScript code from  a string to regular TS file, so we can typecheck it like rest of code (added typescript fixes too).
6. Now, MonetizationEvent interface is available on global.
  • Loading branch information
sidvishnoi committed Jun 18, 2024
1 parent bdb0fe2 commit 13d397d
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 134 deletions.
21 changes: 17 additions & 4 deletions src/background/services/paymentSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import { OpenPaymentsClientError } from '@interledger/open-payments/dist/client'
import { sendMonetizationEvent } from '../lib/messages'
import { convert, sleep } from '@/shared/helpers'
import { transformBalance } from '@/popup/lib/utils'

const DEFAULT_INTERVAL_MS = 1000
const HOUR_MS = 3600 * 1000
Expand Down Expand Up @@ -173,8 +174,14 @@ export class PaymentSession {
frameId: this.frameId,
payload: {
requestId: this.requestId,
details: {
receiveAmount,
detail: {
amountSent: {
currency: receiveAmount.assetCode,
value: transformBalance(
receiveAmount.value,
receiveAmount.assetScale
)
},
incomingPayment,
paymentPointer: this.receiver.id
}
Expand Down Expand Up @@ -283,8 +290,14 @@ export class PaymentSession {
frameId: this.frameId,
payload: {
requestId: this.requestId,
details: {
receiveAmount,
detail: {
amountSent: {
currency: receiveAmount.assetCode,
value: transformBalance(
receiveAmount.value,
receiveAmount.assetScale
)
},
incomingPayment,
paymentPointer: this.receiver.id
}
Expand Down
125 changes: 125 additions & 0 deletions src/content/polyfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import type { MonetizationEventPayload } from '@/shared/messages'
;(function () {
const handlers = new WeakMap()
const attributes: PropertyDescriptor & ThisType<EventTarget> = {
enumerable: true,
configurable: false,
get() {
return handlers.get(this) || null
},
set(val) {
const listener = handlers.get(this)
if (listener && listener === val) {
// nothing to do here ?
return
}
const removeAnyExisting = () => {
if (listener) {
this.removeEventListener('monetization', listener)
}
}
if (val == null /* OR undefined*/) {
handlers.delete(this)
removeAnyExisting()
} else if (typeof val === 'function') {
removeAnyExisting()
this.addEventListener('monetization', val)
handlers.set(this, val)
} else {
throw new Error('val must be a function, got ' + typeof val)
}
}
}

const supportsOriginal = DOMTokenList.prototype.supports
const supportsMonetization = Symbol.for('link-supports-monetization')
DOMTokenList.prototype.supports = function (token) {
// @ts-expect-error: polyfilled
if (this[supportsMonetization] && token === 'monetization') {
return true
} else {
return supportsOriginal.call(this, token)
}
}

const relList = Object.getOwnPropertyDescriptor(
HTMLLinkElement.prototype,
'relList'
)!
const relListGetOriginal = relList.get!

relList.get = function () {
const val = relListGetOriginal.call(this)
val[supportsMonetization] = true
return val
}

Object.defineProperty(HTMLLinkElement.prototype, 'relList', relList)
Object.defineProperty(HTMLElement.prototype, 'onmonetization', attributes)
Object.defineProperty(Window.prototype, 'onmonetization', attributes)
Object.defineProperty(Document.prototype, 'onmonetization', attributes)

let eventDetailDeprecationEmitted = false
class MonetizationEvent extends Event {
public readonly amountSent: PaymentCurrencyAmount
public readonly incomingPayment: string
public readonly paymentPointer: string

constructor(
type: 'monetization',
eventInitDict: MonetizationEventPayload['detail']
) {
super(type, { bubbles: true })
const { amountSent, incomingPayment, paymentPointer } = eventInitDict
this.amountSent = amountSent
this.incomingPayment = incomingPayment
this.paymentPointer = paymentPointer
}

get [Symbol.toStringTag]() {
return 'MonetizationEvent'
}

get detail() {
if (!eventDetailDeprecationEmitted) {
const msg = `MonetizationEvent.detail is deprecated. Access attributes directly instead.`
// eslint-disable-next-line no-console
console.warn(msg)
eventDetailDeprecationEmitted = true
}
const { amountSent, incomingPayment, paymentPointer } = this
return { amountSent, incomingPayment, paymentPointer }
}
}

// @ts-expect-error: we're defining this now
window.MonetizationEvent = MonetizationEvent

window.addEventListener(
'__wm_ext_monetization',
(event: CustomEvent<MonetizationEventPayload['detail']>) => {
if (!(event.target instanceof HTMLLinkElement)) return
if (!event.target.isConnected) return

const monetizationTag = event.target
monetizationTag.dispatchEvent(
new MonetizationEvent('monetization', event.detail)
)
},
{ capture: true }
)

window.addEventListener(
'__wm_ext_onmonetization_attr_change',
(event: CustomEvent<{ attribute?: string }>) => {
if (!event.target) return

const { attribute } = event.detail
// @ts-expect-error: we're defining this now
event.target.onmonetization = attribute
? new Function(attribute).bind(event.target)
: null
},
{ capture: true }
)
})()
16 changes: 15 additions & 1 deletion src/content/services/contentScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export class ContentScript {
this.bindMessageHandler()
}

start() {
async start() {
await this.injectPolyfill()
if (this.isFirstLevelFrame) {
this.logger.info('Content script started')

Expand Down Expand Up @@ -58,4 +59,17 @@ export class ContentScript {
}
)
}

// TODO: When Firefox has good support for `world: MAIN`, inject this directly
// via manifest.json https://bugzilla.mozilla.org/show_bug.cgi?id=1736575
async injectPolyfill() {
const document = this.window.document
const script = document.createElement('script')
script.src = this.browser.runtime.getURL('polyfill/polyfill.js')
await new Promise<void>((resolve) => {
script.addEventListener('load', () => resolve(), { once: true })
document.documentElement.appendChild(script)
})
script.remove()
}
}
16 changes: 8 additions & 8 deletions src/content/services/monetizationTagManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ export class MonetizationTagManager extends EventEmitter {
}
}

dispatchMonetizationEvent({ requestId, details }: MonetizationEventPayload) {
dispatchMonetizationEvent({ requestId, detail }: MonetizationEventPayload) {
this.monetizationTags.forEach((tagDetails, tag) => {
if (tagDetails.requestId !== requestId) return

const customEvent = new CustomEvent('monetization', {
bubbles: true,
detail: mozClone(details, this.document)
})

tag.dispatchEvent(customEvent)
tag.dispatchEvent(
new CustomEvent('__wm_ext_monetization', {
detail: mozClone(detail, this.document),
bubbles: true
})
)
})
return
}
Expand Down Expand Up @@ -296,7 +296,7 @@ export class MonetizationTagManager extends EventEmitter {

if (!attribute && !changeDetected) return

const customEvent = new CustomEvent('onmonetization-attr-changed', {
const customEvent = new CustomEvent('__wm_ext_onmonetization_attr_change', {
bubbles: true,
detail: mozClone({ attribute }, this.document)
})
Expand Down
13 changes: 0 additions & 13 deletions src/content/static/index.ts

This file was deleted.

89 changes: 0 additions & 89 deletions src/content/static/polyfill.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ try {
cloneIntoRef = undefined
}

export function mozClone(obj: unknown, document: Document) {
export function mozClone<T = unknown>(obj: T, document: Document) {
return cloneIntoRef ? cloneIntoRef(obj, document.defaultView) : obj
}

Expand Down
15 changes: 1 addition & 14 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@
{
"matches": ["https://*/*"],
"js": ["content/content.js"],
"all_frames": true
},
{
"run_at": "document_start",
"matches": ["https://*/*"],
"js": ["contentStatic/contentStatic.js"],
"world": "MAIN",
"all_frames": true
}
],
Expand All @@ -33,14 +27,7 @@
},
"web_accessible_resources": [
{
"resources": [
"assets/*",
"content/*",
"options/*",
"popup/*",
"background/*",
"specs/*"
],
"resources": ["assets/*", "polyfill/*", "specs/*"],
"matches": ["<all_urls>"]
}
],
Expand Down
8 changes: 6 additions & 2 deletions src/shared/messages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { WalletAddress } from '@interledger/open-payments'
import type { WalletAddress, OutgoingPayment } from '@interledger/open-payments'
import { type Browser } from 'webextension-polyfill'

export interface SuccessResponse<TPayload = undefined> {
Expand Down Expand Up @@ -131,7 +131,11 @@ export enum BackgroundToContentAction {

export interface MonetizationEventPayload {
requestId: string
details: any
detail: {
amountSent: PaymentCurrencyAmount
incomingPayment: OutgoingPayment['receiver']
paymentPointer: WalletAddress['id']
}
}

export interface EmitToggleWMPayload {
Expand Down
Loading

0 comments on commit 13d397d

Please sign in to comment.