Skip to content

Commit

Permalink
feat(mv3): blocking by observing (#1181)
Browse files Browse the repository at this point in the history
* feat(mv3): ✨ MV3 Manifest Migration

* fix(mv3): 🗑️ No longer needed

* fix(mv3): 🔧 Corresponding MV3 Changes

* feat(mv3): 📦 Adding deps

* feat(telemetry): Refactor Metrics Tracking from background service_worker (#1172)

* feat(telemetry): ♻️ Init Telemetry away from background service_worker.

* feat(telemetry): ♻️ Track metrics from page context instead of service_worker context

* feat(mv3): 🩹 Patch @protobufjs/inquire to not have eval

* fix(mv3): 👽 Fixing contextMenus API changes (#1177)

* fix(mv3): 👽 Fixing contextMenus API changes

* fix(mv3): 🩹 Fixing the browser.action api

* fix(mv3): webpack configs (#1178)

* fix(mv3): 👽 Fixing contextMenus API changes

* fix(mv3): 🩹 Fixing the browser.action api

* fix(mv3): 🔧 Fixing webpack config

* fix(mv3): 🩹 Patching debug package and making background sw work.

* feat(mv3): ✨ XHR to Fetch Migration (#1179)

* fix(mv3): 👽 Fixing contextMenus API changes

* fix(mv3): 🩹 Fixing the browser.action api

* fix(mv3): 🔧 Fixing webpack config

* fix(mv3): 🩹 Patching debug package and making background sw work.

* feat(mv3): ✨ XMLHttpRequest => fetch

* fix(mv3): 🚧 Related changes to ipfs-path

* fix(mv3): 🚧 Other Related changes

* fix(mv3): 🚧 Changes to companion

* fix(mv3): ✅ Fixing tests to account for async code.

* Fix(mv3): Popup Was Broken (#1180)

* fix(mv3): 👽 Fixing contextMenus API changes

* fix(mv3): 🩹 Fixing the browser.action api

* fix(mv3): 🔧 Fixing webpack config

* fix(mv3): 🩹 Patching debug package and making background sw work.

* feat(mv3): ✨ XMLHttpRequest => fetch

* fix(mv3): 🚧 Related changes to ipfs-path

* fix(mv3): 🚧 Other Related changes

* fix(mv3): 🚧 Changes to companion

* fix(mv3): ✅ Fixing tests to account for async code.

* feat(mv3): ♻️ Implementing a non-windowed companion instance

* fix(mv3): 🗑️ Removing calls to background page.

* fix: 🗑️ Unneeded debug statement

* fix(mv3): 🛂 Limiting permissions to chrome-extension

* Update add-on/src/lib/ipfs-companion.js

Co-authored-by: Russell Dempsey <[email protected]>

* fix(types): 🏷️ Refactoring existing type declaration

* fix(types): 🏷️ Moving to new types path

* feat(types): ✨ Adding typescript support for transpilation

* feat(mv3): ✨ Adding blocking request tester

* fix(mv3): 🩹 package.json

* fix(mv3): 🚨 Fix Lint

* fix: 🚨 fix lint

* fix(mv3): 🩹 temp fix to build background context

* fix(mv3): 👔 Detection Logic for MV3 world.

* feat(mv3): ✨ Dynamic RegexSubstitution

* fix(types): ⬆️ Adding .mocharc.json to fix mocha for TS.

* fix: 🚨 Lint Fix

* fix(mv3): ♻️ refactor background.service_worker

* feat(mv3): ✨ Passing state to BlockOrObserve

* fix(recovery): 🐛 conditional for recovery

* fix: 🗑️ unneeded @ts-ignore

* fix: 💡 Adding comments

* fix: fixing string method.

* fix: removing extra space.

* fix: removing @ts-nocheck

---------

Co-authored-by: Russell Dempsey <[email protected]>
  • Loading branch information
whizzzkid and SgtPooki authored Apr 4, 2023
1 parent f670059 commit 52a9aa8
Show file tree
Hide file tree
Showing 15 changed files with 1,037 additions and 150 deletions.
17 changes: 17 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"diff": true,
"extensions": [".js", ".ts"],
"package": "./package.json",
"require": [
"ignore-styles",
"ts-node/register",
"tsconfig-paths/register"
],
"exit": true,
"recursive": true,
"node-option": [
"es-module-specifier-resolution=node",
"experimental-specifier-resolution=node",
"loader=ts-node/esm"
]
}
6 changes: 4 additions & 2 deletions add-on/manifest.chromium.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"minimum_chrome_version": "72",
"minimum_chrome_version": "101",
"permissions": [
"clipboardWrite",
"contextMenus",
Expand All @@ -9,7 +9,9 @@
"tabs",
"unlimitedStorage",
"webNavigation",
"webRequest"
"webRequest",
"declarativeNetRequest",
"declarativeNetRequestFeedback"
],
"host_permissions": ["<all_urls>"],
"incognito": "not_allowed"
Expand Down
6 changes: 3 additions & 3 deletions add-on/manifest.common.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
"description": "__MSG_manifest_extensionDescription__",
"homepage_url": "https://github.com/ipfs-shipyard/ipfs-companion",
"author": "IPFS Community",
"background": {
"service_worker": "dist/bundles/backgroundPage.bundle.js"
},
"icons": {
"19": "icons/png/ipfs-logo-on_19.png",
"38": "icons/png/ipfs-logo-on_38.png",
"128": "icons/png/ipfs-logo-on_128.png"
},
"background": {
"service_worker": "dist/bundles/backgroundPage.bundle.js"
},
"action": {
"default_icon": {
"19": "icons/png/ipfs-logo-off_19.png",
Expand Down
22 changes: 18 additions & 4 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import createRuntimeChecks from './runtime-checks.js'
import { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway, contextMenuCopyPermalink, contextMenuCopyCidAddress } from './context-menus.js'
import { registerSubdomainProxy } from './http-proxy.js'
import { runPendingOnInstallTasks } from './on-installed.js'
import { getExtraInfoSpec } from './redirect-handler/blockOrObserve.js'

const log = debug('ipfs-companion:main')
log.error = debug('ipfs-companion:main:error')

Expand All @@ -33,7 +35,7 @@ export default async function init (windowedContext = false) {
// INIT
// ===================================================================
let ipfs // ipfs-api instance
/** @type {import('../types.js').CompanionState} */
/** @type {import('../types/companion.js').CompanionState} */
let state // avoid redundant API reads by utilizing local cache of various states
let dnslinkResolver
let ipfsPathValidator
Expand All @@ -54,6 +56,7 @@ export default async function init (windowedContext = false) {
await migrateOptions(browser.storage.local, debug)
const options = await browser.storage.local.get(optionDefaults)
runtime = await createRuntimeChecks(browser)

state = initState(options)
notify = createNotifier(getState)

Expand All @@ -65,6 +68,7 @@ export default async function init (windowedContext = false) {
console.error('[ipfs-companion] Failed to init IPFS client', err)
notify(
'notify_startIpfsNodeErrorTitle',

err.name === 'ValidationError' ? err.details[0].message : err.message
)
}
Expand Down Expand Up @@ -115,9 +119,11 @@ export default async function init (windowedContext = false) {
// Note: we need this for code ensuring kubo-rpc-client can talk to API without setting CORS
onBeforeSendInfoSpec.push('extraHeaders')
}
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { urls: ['<all_urls>'] }, onBeforeSendInfoSpec)
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] })
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, ['responseHeaders'])
browser.webRequest.onBeforeSendHeaders.addListener(

onBeforeSendHeaders, { urls: ['<all_urls>'] }, getExtraInfoSpec(onBeforeSendInfoSpec))
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] }, getExtraInfoSpec())
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, getExtraInfoSpec(['responseHeaders']))
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'], types: ['main_frame'] })
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'], types: ['main_frame'] })
browser.storage.onChanged.addListener(onStorageChange)
Expand Down Expand Up @@ -307,6 +313,7 @@ export default async function init (windowedContext = false) {
}
// console.log('onAddFromContext.context', context)
// console.log('onAddFromContext.fetchOptions', fetchOptions)

const response = await fetch(dataSrc, fetchOptions)
const blob = await response.blob()
const url = new URL(response.url)
Expand All @@ -315,6 +322,7 @@ export default async function init (windowedContext = false) {
? url.hostname
: url.pathname.replace(/[\\/]+$/, '').split('/').pop()
data = {

path: decodeURIComponent(filename),
content: blob
}
Expand All @@ -332,6 +340,7 @@ export default async function init (windowedContext = false) {
}
} catch (error) {
console.error('Error in import to IPFS context menu', error)

if (error.message === 'NetworkError when attempting to fetch resource.') {
notify('notify_importErrorTitle', 'notify_importTrackingProtectionErrorMsg')
console.warn('IPFS import often fails because remote file can not be downloaded due to Tracking Protection. See details at: https://github.com/ipfs/ipfs-companion/issues/227')
Expand Down Expand Up @@ -524,6 +533,7 @@ export default async function init (windowedContext = false) {
// Chromium does not support SVG [ticket below is 8 years old, I can't even..]
// https://bugs.chromium.org/p/chromium/issues/detail?id=29683
// Still, we want icon, so we precompute rasters of popular sizes and use them instead

iconDefinition = await rasterIconDefinition(iconPath)
await browser.action.setIcon(iconDefinition)
}
Expand All @@ -537,6 +547,7 @@ export default async function init (windowedContext = false) {
if (state.automaticMode && state.localGwAvailable) {
if (oldPeerCount === offlinePeerCount && newPeerCount > offlinePeerCount && !state.redirect) {
await browser.storage.local.set({ useCustomGateway: true })

reloadIpfsClientOfflinePages(browser, ipfs, state)
} else if (newPeerCount === offlinePeerCount && state.redirect) {
await browser.storage.local.set({ useCustomGateway: false })
Expand Down Expand Up @@ -634,6 +645,7 @@ export default async function init (windowedContext = false) {
await destroyIpfsClient(browser)
} catch (err) {
console.error('[ipfs-companion] Failed to destroy IPFS client', err)

notify('notify_stopIpfsNodeErrorTitle', err.message)
} finally {
ipfs = null
Expand All @@ -648,6 +660,7 @@ export default async function init (windowedContext = false) {
console.error('[ipfs-companion] Failed to init IPFS client', err)
notify(
'notify_startIpfsNodeErrorTitle',

err.name === 'ValidationError' ? err.details[0].message : err.message
)
}
Expand Down Expand Up @@ -718,6 +731,7 @@ export default async function init (windowedContext = false) {
const rasterIconDefinition = pMemoize((svgPath) => {
const pngPath = (size) => {
// point at precomputed PNG file

const baseName = /\/icons\/(.+)\.svg/.exec(svgPath)[1]
return `/icons/png/${baseName}_${size}.png`
}
Expand Down
49 changes: 43 additions & 6 deletions add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { dropSlash, ipfsUri, pathAtHttpGateway, sameGateway } from './ipfs-path.
import { safeURL } from './options.js'
import { braveNodeType } from './ipfs-client/brave.js'
import { recoveryPagePath } from './constants.js'
import { addRuleToDynamicRuleSetGenerator, supportsBlock } from './redirect-handler/blockOrObserve.js'

const log = debug('ipfs-companion:request')
log.error = debug('ipfs-companion:request:error')
Expand All @@ -30,13 +31,15 @@ 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

// 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
export function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, runtime) {
const browser = runtime.browser
const runtimeRoot = browser.runtime.getURL('/')
const webExtensionOrigin = runtimeRoot ? new URL(runtimeRoot).origin : 'http://companion-origin' // avoid 'null' because it has special meaning
addRuleToDynamicRuleSet = addRuleToDynamicRuleSetGenerator(getState)
const isCompanionRequest = (request) => {
// We inspect webRequest object (WebExtension API) instead of Origin HTTP
// header because the value of the latter changed over the years ad
Expand Down Expand Up @@ -146,20 +149,34 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
// 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 { redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}` }
return handleRedirection({
originUrl: request.url,
redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}`
})
}

// When Subdomain Proxy is enabled we normalize address bar requests made
// to the local gateway and replace raw IP with 'localhost' hostname to
// take advantage of subdomain redirect provided by go-ipfs >= 0.5
if (state.redirect && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
const redirectUrl = safeURL(request.url, { useLocalhostName: state.useSubdomains }).toString()
if (redirectUrl !== request.url) return { redirectUrl }
if (redirectUrl !== request.url) {
return handleRedirection({
originUrl: request.url,
redirectUrl
})
}
}

// For now normalize API to the IP to comply with go-ipfs checks
if (state.redirect && request.type === 'main_frame' && sameGateway(request.url, state.apiURL)) {
const redirectUrl = safeURL(request.url, { useLocalhostName: false }).toString()
if (redirectUrl !== request.url) return { redirectUrl }
if (redirectUrl !== request.url) {
return handleRedirection({
originUrl: request.url,
redirectUrl
})
}
}

// early sanity checks
Expand Down Expand Up @@ -459,6 +476,15 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
}
}

function handleRedirection ({ originUrl, redirectUrl }) {
if (supportsBlock) {
return { redirectUrl }
}

// Let browser handle redirection MV3 style.
addRuleToDynamicRuleSet({ originUrl, redirectUrl })
}

// Returns a string with URL at the active gateway (local or public)
async function redirectToGateway (request, url, state, ipfsPathValidator, runtime) {
const { resolveToPublicUrl, resolveToLocalUrl } = ipfsPathValidator
Expand Down Expand Up @@ -507,7 +533,12 @@ async function redirectToGateway (request, url, state, ipfsPathValidator, runtim
}

// return a redirect only if URL changed
if (redirectUrl && request.url !== redirectUrl) return { redirectUrl }
if (redirectUrl && request.url !== redirectUrl) {
return handleRedirection({
originUrl: request.url,
redirectUrl
})
}
}

function isSafeToRedirect (request, runtime) {
Expand Down Expand Up @@ -574,7 +605,10 @@ function normalizedRedirectingProtocolRequest (request, pubGwUrl) {
// additional fixups of the final path
path = fixupDnslinkPath(path) // /ipfs/example.com → /ipns/example.com
if (oldPath !== path && isIPFS.path(path)) {
return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) }
return handleRedirection({
originUrl: request.url,
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
})
}
return null
}
Expand Down Expand Up @@ -616,7 +650,10 @@ function normalizedUnhandledIpfsProtocol (request, pubGwUrl) {
if (isIPFS.path(path)) {
// replace search query with a request to a public gateway
// (will be redirected later, if needed)
return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) }
return handleRedirection({
originUrl: request.url,
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion add-on/src/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import isFQDN from 'is-fqdn'
import { isIPv4, isIPv6 } from 'is-ip'

/**
* @type {Readonly<import('../types.js').CompanionOptions>}
* @type {Readonly<import('../types/companion.js').CompanionOptions>}
*/
export const optionDefaults = Object.freeze({
active: true, // global ON/OFF switch, overrides everything else
Expand Down
Loading

0 comments on commit 52a9aa8

Please sign in to comment.