Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mv3): Tracking URL resolved/observed count. #1245

Merged
merged 20 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import LRU from 'lru-cache'
import pMemoize from 'p-memoize'
import toMultiaddr from 'uri-to-multiaddr'
import browser from 'webextension-polyfill'
import { handleConsentFromState, trackView } from '../lib/telemetry.js'
import { contextMenuCopyAddressAtPublicGw, contextMenuCopyCanonicalAddress, contextMenuCopyCidAddress, contextMenuCopyPermalink, contextMenuCopyRawCid, contextMenuViewOnGateway, createContextMenus, findValueForContext } from './context-menus.js'
import createCopier from './copier.js'
import createDnslinkResolver from './dnslink.js'
Expand All @@ -24,7 +25,6 @@ import { guiURLString, migrateOptions, optionDefaults, safeURL, storeMissingOpti
import { getExtraInfoSpec } from './redirect-handler/blockOrObserve.js'
import createRuntimeChecks from './runtime-checks.js'
import { initState, offlinePeerCount } from './state.js'
import { handleConsentFromState, trackView } from '../lib/telemetry.js'

// this won't work in webworker context. Needs to be enabled manually
// https://github.com/debug-js/debug/issues/916
Expand Down
34 changes: 23 additions & 11 deletions add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

import debug from 'debug'

import LRU from 'lru-cache'
import isIPFS from 'is-ipfs'
import isFQDN from 'is-fqdn'
import isIPFS from 'is-ipfs'
import LRU from 'lru-cache'
import { recoveryPagePath } from './constants.js'
import { braveNodeType } from './ipfs-client/brave.js'
import { dropSlash, ipfsUri, pathAtHttpGateway, sameGateway } from './ipfs-path.js'
import { safeURL } from './options.js'
import { braveNodeType } from './ipfs-client/brave.js'
import { recoveryPagePath } from './constants.js'
import { addRuleToDynamicRuleSetGenerator, isLocalHost, supportsBlock } from './redirect-handler/blockOrObserve.js'
import { RequestTracker } from './trackers/requestTracker.js'

const log = debug('ipfs-companion:request')
log.error = debug('ipfs-companion:request:error')
Expand All @@ -32,6 +33,8 @@ const recoverableHttpError = (code) => code && code >= 400
// Tracking late redirects for edge cases such as https://github.com/ipfs-shipyard/ipfs-companion/issues/436
const onHeadersReceivedRedirect = new Set()
let addRuleToDynamicRuleSet = null
const observedRequestTracker = new RequestTracker('url-observed')
const resolvedRequestTracker = new RequestTracker('url-resolved')

// Request modifier provides event listeners for the various stages of making an HTTP request
// API Details: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest
Expand Down Expand Up @@ -144,14 +147,16 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
async onBeforeRequest (request) {
const state = getState()
if (!state.active) return
observedRequestTracker.track(request)

// When local IPFS node is unreachable , show recovery page where user can redirect
// to public gateway.
if (!state.nodeActive && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
const publicUri = await ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
return handleRedirection({
originUrl: request.url,
redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}`
redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}`,
request
})
}

Expand All @@ -162,7 +167,8 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
const redirectUrl = safeURL(request.url, { useLocalhostName: state.useSubdomains }).toString()
return handleRedirection({
originUrl: request.url,
redirectUrl
redirectUrl,
request
})
}

Expand All @@ -171,7 +177,8 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
const redirectUrl = safeURL(request.url, { useLocalhostName: false }).toString()
return handleRedirection({
originUrl: request.url,
redirectUrl
redirectUrl,
request
})
}

Expand Down Expand Up @@ -478,8 +485,9 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
* @param {object} input contains originUrl and redirectUrl.
* @returns
*/
function handleRedirection ({ originUrl, redirectUrl }) {
function handleRedirection ({ originUrl, redirectUrl, request }) {
if (redirectUrl !== '' && originUrl !== '' && redirectUrl !== originUrl) {
resolvedRequestTracker.track(request)
if (supportsBlock) {
return { redirectUrl }
}
Expand Down Expand Up @@ -538,7 +546,8 @@ async function redirectToGateway (request, url, state, ipfsPathValidator, runtim

return handleRedirection({
originUrl: request.url,
redirectUrl
redirectUrl,
request
})
}

Expand Down Expand Up @@ -608,7 +617,8 @@ function normalizedRedirectingProtocolRequest (request, pubGwUrl) {
if (oldPath !== path && isIPFS.path(path)) {
return handleRedirection({
originUrl: request.url,
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
redirectUrl: pathAtHttpGateway(path, pubGwUrl),
request
})
}
return null
Expand Down Expand Up @@ -653,7 +663,9 @@ function normalizedUnhandledIpfsProtocol (request, pubGwUrl) {
// (will be redirected later, if needed)
return handleRedirection({
originUrl: request.url,
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
redirectUrl: pathAtHttpGateway(path, pubGwUrl),
request

})
}
}
Expand Down
12 changes: 12 additions & 0 deletions add-on/src/lib/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import debug from 'debug'
import { WebExtensionStorageProvider } from './storage-provider/WebExtensionStorageProvider.js'
import { CompanionState } from '../types/companion.js'
import { consentTypes } from '@ipfs-shipyard/ignite-metrics'
import type { CountlyEvent } from 'countly-web-sdk'

const log = debug('ipfs-companion:telemetry')

Expand Down Expand Up @@ -51,3 +52,14 @@ export function trackView (view: string, segments: Record<string, string>): void
log('trackView called for view: ', view)
metricsProvider.trackView(view, ignoredViewsRegex, segments)
}

/**
* TrackView is a wrapper around ignite-metrics trackView
*
* @param event
* @param segments
*/
export function trackEvent (event: CountlyEvent): void {
log('trackEvent called for event: ', event)
metricsProvider.trackEvent(event)
}
49 changes: 49 additions & 0 deletions add-on/src/lib/trackers/requestTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import debug from 'debug'
import type browser from 'webextension-polyfill'
import { trackEvent } from '../telemetry.js'

export class RequestTracker {
private readonly eventKey: 'url-observed' | 'url-resolved'
private readonly flushInterval: number
private readonly log: debug.Debugger & { error?: debug.Debugger }
private lastSync: number = Date.now()
private requestTypeStore: { [key in browser.WebRequest.ResourceType]?: number } = {}

constructor (eventKey: 'url-observed' | 'url-resolved', flushInterval = 1000 * 60 * 5) {
this.eventKey = eventKey
this.log = debug(`ipfs-companion:request-tracker:${eventKey}`)
this.log.error = debug(`ipfs-companion:request-tracker:${eventKey}:error`)
this.flushInterval = flushInterval
this.setupFlushScheduler()
}

track ({ type }: browser.WebRequest.OnBeforeRequestDetailsType): void {
this.log(`track ${type}`, JSON.stringify(this.requestTypeStore))
this.requestTypeStore[type] = (this.requestTypeStore[type] ?? 0) + 1
}

private flushStore (): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flushes events as combined entity on.

this.log('flushing')
const count = Object.values(this.requestTypeStore).reduce((a, b): number => a + b, 0)
if (count === 0) {
this.log('nothing to flush')
return
}
trackEvent({
key: this.eventKey,
count,
dur: Date.now() - this.lastSync,
segmentation: Object.assign({}, this.requestTypeStore) as unknown as Record<string, string>
})
// reset
this.lastSync = Date.now()
this.requestTypeStore = {}
}

private setupFlushScheduler (): void {
setTimeout(() => {
this.flushStore()
this.setupFlushScheduler()
}, this.flushInterval)
}
}
9 changes: 9 additions & 0 deletions add-on/src/types/countly.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
declare module 'countly-web-sdk' {
export interface CountlyEvent {
key: string
count?: number
sum?: number
dur?: number
segmentation?: Record<string, string>
}
}
Loading