Skip to content

Commit 52a9aa8

Browse files
whizzzkidSgtPooki
andauthored
feat(mv3): blocking by observing (#1181)
* 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 <1173416+SgtPooki@users.noreply.github.com> * 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 <1173416+SgtPooki@users.noreply.github.com>
1 parent f670059 commit 52a9aa8

15 files changed

+1037
-150
lines changed

.mocharc.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"diff": true,
3+
"extensions": [".js", ".ts"],
4+
"package": "./package.json",
5+
"require": [
6+
"ignore-styles",
7+
"ts-node/register",
8+
"tsconfig-paths/register"
9+
],
10+
"exit": true,
11+
"recursive": true,
12+
"node-option": [
13+
"es-module-specifier-resolution=node",
14+
"experimental-specifier-resolution=node",
15+
"loader=ts-node/esm"
16+
]
17+
}

add-on/manifest.chromium.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"minimum_chrome_version": "72",
2+
"minimum_chrome_version": "101",
33
"permissions": [
44
"clipboardWrite",
55
"contextMenus",
@@ -9,7 +9,9 @@
99
"tabs",
1010
"unlimitedStorage",
1111
"webNavigation",
12-
"webRequest"
12+
"webRequest",
13+
"declarativeNetRequest",
14+
"declarativeNetRequestFeedback"
1315
],
1416
"host_permissions": ["<all_urls>"],
1517
"incognito": "not_allowed"

add-on/manifest.common.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
"description": "__MSG_manifest_extensionDescription__",
77
"homepage_url": "https://github.com/ipfs-shipyard/ipfs-companion",
88
"author": "IPFS Community",
9+
"background": {
10+
"service_worker": "dist/bundles/backgroundPage.bundle.js"
11+
},
912
"icons": {
1013
"19": "icons/png/ipfs-logo-on_19.png",
1114
"38": "icons/png/ipfs-logo-on_38.png",
1215
"128": "icons/png/ipfs-logo-on_128.png"
1316
},
14-
"background": {
15-
"service_worker": "dist/bundles/backgroundPage.bundle.js"
16-
},
1717
"action": {
1818
"default_icon": {
1919
"19": "icons/png/ipfs-logo-off_19.png",

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import createRuntimeChecks from './runtime-checks.js'
2323
import { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway, contextMenuCopyPermalink, contextMenuCopyCidAddress } from './context-menus.js'
2424
import { registerSubdomainProxy } from './http-proxy.js'
2525
import { runPendingOnInstallTasks } from './on-installed.js'
26+
import { getExtraInfoSpec } from './redirect-handler/blockOrObserve.js'
27+
2628
const log = debug('ipfs-companion:main')
2729
log.error = debug('ipfs-companion:main:error')
2830

@@ -33,7 +35,7 @@ export default async function init (windowedContext = false) {
3335
// INIT
3436
// ===================================================================
3537
let ipfs // ipfs-api instance
36-
/** @type {import('../types.js').CompanionState} */
38+
/** @type {import('../types/companion.js').CompanionState} */
3739
let state // avoid redundant API reads by utilizing local cache of various states
3840
let dnslinkResolver
3941
let ipfsPathValidator
@@ -54,6 +56,7 @@ export default async function init (windowedContext = false) {
5456
await migrateOptions(browser.storage.local, debug)
5557
const options = await browser.storage.local.get(optionDefaults)
5658
runtime = await createRuntimeChecks(browser)
59+
5760
state = initState(options)
5861
notify = createNotifier(getState)
5962

@@ -65,6 +68,7 @@ export default async function init (windowedContext = false) {
6568
console.error('[ipfs-companion] Failed to init IPFS client', err)
6669
notify(
6770
'notify_startIpfsNodeErrorTitle',
71+
6872
err.name === 'ValidationError' ? err.details[0].message : err.message
6973
)
7074
}
@@ -115,9 +119,11 @@ export default async function init (windowedContext = false) {
115119
// Note: we need this for code ensuring kubo-rpc-client can talk to API without setting CORS
116120
onBeforeSendInfoSpec.push('extraHeaders')
117121
}
118-
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { urls: ['<all_urls>'] }, onBeforeSendInfoSpec)
119-
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] })
120-
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, ['responseHeaders'])
122+
browser.webRequest.onBeforeSendHeaders.addListener(
123+
124+
onBeforeSendHeaders, { urls: ['<all_urls>'] }, getExtraInfoSpec(onBeforeSendInfoSpec))
125+
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] }, getExtraInfoSpec())
126+
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, getExtraInfoSpec(['responseHeaders']))
121127
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'], types: ['main_frame'] })
122128
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'], types: ['main_frame'] })
123129
browser.storage.onChanged.addListener(onStorageChange)
@@ -307,6 +313,7 @@ export default async function init (windowedContext = false) {
307313
}
308314
// console.log('onAddFromContext.context', context)
309315
// console.log('onAddFromContext.fetchOptions', fetchOptions)
316+
310317
const response = await fetch(dataSrc, fetchOptions)
311318
const blob = await response.blob()
312319
const url = new URL(response.url)
@@ -315,6 +322,7 @@ export default async function init (windowedContext = false) {
315322
? url.hostname
316323
: url.pathname.replace(/[\\/]+$/, '').split('/').pop()
317324
data = {
325+
318326
path: decodeURIComponent(filename),
319327
content: blob
320328
}
@@ -332,6 +340,7 @@ export default async function init (windowedContext = false) {
332340
}
333341
} catch (error) {
334342
console.error('Error in import to IPFS context menu', error)
343+
335344
if (error.message === 'NetworkError when attempting to fetch resource.') {
336345
notify('notify_importErrorTitle', 'notify_importTrackingProtectionErrorMsg')
337346
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')
@@ -524,6 +533,7 @@ export default async function init (windowedContext = false) {
524533
// Chromium does not support SVG [ticket below is 8 years old, I can't even..]
525534
// https://bugs.chromium.org/p/chromium/issues/detail?id=29683
526535
// Still, we want icon, so we precompute rasters of popular sizes and use them instead
536+
527537
iconDefinition = await rasterIconDefinition(iconPath)
528538
await browser.action.setIcon(iconDefinition)
529539
}
@@ -537,6 +547,7 @@ export default async function init (windowedContext = false) {
537547
if (state.automaticMode && state.localGwAvailable) {
538548
if (oldPeerCount === offlinePeerCount && newPeerCount > offlinePeerCount && !state.redirect) {
539549
await browser.storage.local.set({ useCustomGateway: true })
550+
540551
reloadIpfsClientOfflinePages(browser, ipfs, state)
541552
} else if (newPeerCount === offlinePeerCount && state.redirect) {
542553
await browser.storage.local.set({ useCustomGateway: false })
@@ -634,6 +645,7 @@ export default async function init (windowedContext = false) {
634645
await destroyIpfsClient(browser)
635646
} catch (err) {
636647
console.error('[ipfs-companion] Failed to destroy IPFS client', err)
648+
637649
notify('notify_stopIpfsNodeErrorTitle', err.message)
638650
} finally {
639651
ipfs = null
@@ -648,6 +660,7 @@ export default async function init (windowedContext = false) {
648660
console.error('[ipfs-companion] Failed to init IPFS client', err)
649661
notify(
650662
'notify_startIpfsNodeErrorTitle',
663+
651664
err.name === 'ValidationError' ? err.details[0].message : err.message
652665
)
653666
}
@@ -718,6 +731,7 @@ export default async function init (windowedContext = false) {
718731
const rasterIconDefinition = pMemoize((svgPath) => {
719732
const pngPath = (size) => {
720733
// point at precomputed PNG file
734+
721735
const baseName = /\/icons\/(.+)\.svg/.exec(svgPath)[1]
722736
return `/icons/png/${baseName}_${size}.png`
723737
}

add-on/src/lib/ipfs-request.js

+43-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { dropSlash, ipfsUri, pathAtHttpGateway, sameGateway } from './ipfs-path.
1010
import { safeURL } from './options.js'
1111
import { braveNodeType } from './ipfs-client/brave.js'
1212
import { recoveryPagePath } from './constants.js'
13+
import { addRuleToDynamicRuleSetGenerator, supportsBlock } from './redirect-handler/blockOrObserve.js'
1314

1415
const log = debug('ipfs-companion:request')
1516
log.error = debug('ipfs-companion:request:error')
@@ -30,13 +31,15 @@ const recoverableHttpError = (code) => code && code >= 400
3031

3132
// Tracking late redirects for edge cases such as https://github.com/ipfs-shipyard/ipfs-companion/issues/436
3233
const onHeadersReceivedRedirect = new Set()
34+
let addRuleToDynamicRuleSet = null
3335

3436
// Request modifier provides event listeners for the various stages of making an HTTP request
3537
// API Details: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest
3638
export function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, runtime) {
3739
const browser = runtime.browser
3840
const runtimeRoot = browser.runtime.getURL('/')
3941
const webExtensionOrigin = runtimeRoot ? new URL(runtimeRoot).origin : 'http://companion-origin' // avoid 'null' because it has special meaning
42+
addRuleToDynamicRuleSet = addRuleToDynamicRuleSetGenerator(getState)
4043
const isCompanionRequest = (request) => {
4144
// We inspect webRequest object (WebExtension API) instead of Origin HTTP
4245
// header because the value of the latter changed over the years ad
@@ -146,20 +149,34 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
146149
// to public gateway.
147150
if (!state.nodeActive && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
148151
const publicUri = await ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
149-
return { redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}` }
152+
return handleRedirection({
153+
originUrl: request.url,
154+
redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}`
155+
})
150156
}
151157

152158
// When Subdomain Proxy is enabled we normalize address bar requests made
153159
// to the local gateway and replace raw IP with 'localhost' hostname to
154160
// take advantage of subdomain redirect provided by go-ipfs >= 0.5
155161
if (state.redirect && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
156162
const redirectUrl = safeURL(request.url, { useLocalhostName: state.useSubdomains }).toString()
157-
if (redirectUrl !== request.url) return { redirectUrl }
163+
if (redirectUrl !== request.url) {
164+
return handleRedirection({
165+
originUrl: request.url,
166+
redirectUrl
167+
})
168+
}
158169
}
170+
159171
// For now normalize API to the IP to comply with go-ipfs checks
160172
if (state.redirect && request.type === 'main_frame' && sameGateway(request.url, state.apiURL)) {
161173
const redirectUrl = safeURL(request.url, { useLocalhostName: false }).toString()
162-
if (redirectUrl !== request.url) return { redirectUrl }
174+
if (redirectUrl !== request.url) {
175+
return handleRedirection({
176+
originUrl: request.url,
177+
redirectUrl
178+
})
179+
}
163180
}
164181

165182
// early sanity checks
@@ -459,6 +476,15 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
459476
}
460477
}
461478

479+
function handleRedirection ({ originUrl, redirectUrl }) {
480+
if (supportsBlock) {
481+
return { redirectUrl }
482+
}
483+
484+
// Let browser handle redirection MV3 style.
485+
addRuleToDynamicRuleSet({ originUrl, redirectUrl })
486+
}
487+
462488
// Returns a string with URL at the active gateway (local or public)
463489
async function redirectToGateway (request, url, state, ipfsPathValidator, runtime) {
464490
const { resolveToPublicUrl, resolveToLocalUrl } = ipfsPathValidator
@@ -507,7 +533,12 @@ async function redirectToGateway (request, url, state, ipfsPathValidator, runtim
507533
}
508534

509535
// return a redirect only if URL changed
510-
if (redirectUrl && request.url !== redirectUrl) return { redirectUrl }
536+
if (redirectUrl && request.url !== redirectUrl) {
537+
return handleRedirection({
538+
originUrl: request.url,
539+
redirectUrl
540+
})
541+
}
511542
}
512543

513544
function isSafeToRedirect (request, runtime) {
@@ -574,7 +605,10 @@ function normalizedRedirectingProtocolRequest (request, pubGwUrl) {
574605
// additional fixups of the final path
575606
path = fixupDnslinkPath(path) // /ipfs/example.com → /ipns/example.com
576607
if (oldPath !== path && isIPFS.path(path)) {
577-
return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) }
608+
return handleRedirection({
609+
originUrl: request.url,
610+
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
611+
})
578612
}
579613
return null
580614
}
@@ -616,7 +650,10 @@ function normalizedUnhandledIpfsProtocol (request, pubGwUrl) {
616650
if (isIPFS.path(path)) {
617651
// replace search query with a request to a public gateway
618652
// (will be redirected later, if needed)
619-
return { redirectUrl: pathAtHttpGateway(path, pubGwUrl) }
653+
return handleRedirection({
654+
originUrl: request.url,
655+
redirectUrl: pathAtHttpGateway(path, pubGwUrl)
656+
})
620657
}
621658
}
622659

add-on/src/lib/options.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import isFQDN from 'is-fqdn'
44
import { isIPv4, isIPv6 } from 'is-ip'
55

66
/**
7-
* @type {Readonly<import('../types.js').CompanionOptions>}
7+
* @type {Readonly<import('../types/companion.js').CompanionOptions>}
88
*/
99
export const optionDefaults = Object.freeze({
1010
active: true, // global ON/OFF switch, overrides everything else

0 commit comments

Comments
 (0)