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

fix(rules): Better Redirect Rules #1256

Merged
merged 20 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7f68ab4
fix(mv3): :wrench: Modifying the default local redirect behaviour.
whizzzkid Aug 4, 2023
d94b9bb
Merge branch 'rc/3.0-mv3' into fix/default-rules
whizzzkid Aug 4, 2023
a817045
Merge branch 'rc/3.0-mv3' into fix/default-rules
whizzzkid Aug 4, 2023
045e660
fix(mv3): :wrench: Modifying the default local redirect behaviour.
whizzzkid Aug 4, 2023
f6561b9
Merge remote-tracking branch 'refs/remotes/origin/fix/default-rules' …
whizzzkid Aug 4, 2023
c020638
Merge branch 'feat/redirection-tests' into fix/default-rules
whizzzkid Aug 4, 2023
fc085b5
fix(mv3): :bug: Making rules less greedy
whizzzkid Aug 10, 2023
007f41f
fix(mv3): :sparkles: Dynamic Rules for subdomain gateways.
whizzzkid Aug 11, 2023
f18579b
fix(types): Adding ambient types for is-ipfs.
whizzzkid Aug 11, 2023
3e72d36
fix(test):
whizzzkid Aug 12, 2023
d36a282
fix(test): helper
whizzzkid Aug 12, 2023
6ee2d31
feat(mv3): less greedy rules
whizzzkid Aug 12, 2023
e592755
feat: Adding simpler regex for redirects from similar namespaces.
whizzzkid Aug 12, 2023
5c85d84
fix(lint): :rotating_light: Warnings
whizzzkid Aug 12, 2023
fca5fe2
feat(mv3): Better Default Rules (#1260)
whizzzkid Aug 15, 2023
832679d
Update add-on/src/lib/redirect-handler/blockOrObserve.ts
whizzzkid Aug 16, 2023
5e22cea
fix(docs): :pencil2: Adding comments
whizzzkid Aug 16, 2023
dbc672c
refactor(regexfilters): Better Structure and Readability (#1261)
whizzzkid Aug 25, 2023
381f63c
fix(mv3): no blanket redirect for subdomains without namespaces.
whizzzkid Aug 25, 2023
4020796
fix(lint): unused import
whizzzkid Aug 25, 2023
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
105 changes: 98 additions & 7 deletions add-on/src/lib/redirect-handler/blockOrObserve.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import debug from 'debug'
import browser from 'webextension-polyfill'
import { CompanionState } from '../../types/companion.js'
import isIPFS from 'is-ipfs'

// this won't work in webworker context. Needs to be enabled manually
// https://github.com/debug-js/debug/issues/916
const log = debug('ipfs-companion:redirect-handler:blockOrObserve')
log.error = debug('ipfs-companion:redirect-handler:blockOrObserve:error')

const DEFAULT_NAMESPACES = new Set(['ipfs', 'ipns'])

export const GLOBAL_STATE_CHANGE = 'GLOBAL_STATE_CHANGE'
export const GLOBAL_STATE_OPTION_CHANGE = 'GLOBAL_STATE_OPTION_CHANGE'
export const DELETE_RULE_REQUEST = 'DELETE_RULE_REQUEST'
export const DELETE_RULE_REQUEST_SUCCESS = 'DELETE_RULE_REQUEST_SUCCESS'

// We need to match the rest of the URL, so we can use a wildcard.
export const RULE_REGEX_ENDING = '((?:[^\\.]|$).*)$'

interface regexFilterMap {
Expand Down Expand Up @@ -108,6 +113,16 @@ function escapeURLRegex (str: string): string {
return str.replace(ALLOWED_CHARS_URL_REGEX, '\\$1')
}

/**
* Compute the namespace from the URL.
*
* @param url string
*/
function computeNamespaceFromUrl (url: string): string {
const { pathname } = new URL(url)
return (/\/([^/]+)\//i.exec(pathname)?.[1] ?? '').toLowerCase()
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we describe a little more what this is doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comment describing it.


/**
* Construct a regex filter and substitution for a redirect.
*
Expand All @@ -119,6 +134,84 @@ function constructRegexFilter ({ originUrl, redirectUrl }: redirectHandlerInput)
regexSubstitution: string
regexFilter: string
} {
let regexSubstitution = redirectUrl
let regexFilter = originUrl
const originURL = new URL(originUrl)
const redirectNS = computeNamespaceFromUrl(redirectUrl)
const originNS = computeNamespaceFromUrl(originUrl)
if (!DEFAULT_NAMESPACES.has(originNS) && DEFAULT_NAMESPACES.has(redirectNS)) {
// A redirect like https://github.com/ipfs/ipfs-companion/issues/1255
regexFilter = `^${escapeURLRegex(regexFilter)}`.replace(/https?/ig, 'https?')
const origRegexFilter = regexFilter
Copy link
Member

Choose a reason for hiding this comment

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

All of the code in this function is hard to follow, and I doubt someone else will be able to maintain it easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in #1261


const [tld, root, ...subdomain] = originURL.hostname.split('.').reverse()
Copy link
Member

Choose a reason for hiding this comment

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

⚠️ I suspect this may not be enough for roots and tlds that have more . in their name, will lead to bugs / false negatives.

For example:

  • cid.ipfs.gateway.example.net → subdomain gateway is on gateway.example.net
  • cid.ipfs.example.co.uk → subdomain gateway is on example.co.uk ("tld" itself may have . as in practice we care about public suffix, and not real tld– see https://publicsuffix.org)

A safer way is to do what is-ipfs does and match subdomainGatewayPattern /^https?:\/\/([^/]+)\.(ip[fn]s)\.[^/?]+/ which is greedy from left, and not right.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lidel yes, it does the same, I am just removing the known parts, maybe subdomain is a wrong choice for a variable name:

Your example: cid.ipfs.gateway.example.net

  • tld: net
  • domain: example
  • subdmain: ['gateway', 'ipfs', 'cid'] (in reverse)
  • go through the list till either (ipfs|ipns) or a valid cid is found.
  • I think I need to add more comments explaining this.

The regex you shared fails on e.g.: https://bafybeib3bzis4mejzsnzsb65od3rnv5ffit7vsllratddjkgfgq4wiamqu.on.fleek.co/ because it does not havd \.(ip[fn]s)\.

PS: Also, I like (ipfs|ipns) more than (ip[fn]s)

const staticUrl = [root, tld]
while (subdomain.length > 0) {
const subdomainPart = subdomain.shift()
Copy link
Member

Choose a reason for hiding this comment

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

I believe subdomain is probably type (string|undefined)[] and needs a check to confirm it's not null, despite the fact that we confirm length in the while condition. We can explicitly set type of subdomainPart to string to avoid as string twice below.

Or, A potentially more type-safe approach could be to do something like

let subdomainPart = subdomain.shift()
while(subdomainPart != null) {
// ... code
 subdomainPart = subdomain.shift()
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed in #1261

const commonStaticUrlStart = `^${originURL.protocol}\\:\\/\\/`
const commonStaticUrlEnd = `\\.${escapeURLRegex(staticUrl.join('.'))}\\/${RULE_REGEX_ENDING}`
if (isIPFS.cid(subdomainPart as string)) {
// We didn't find a namespace, but we found a CID
// e.g. https://bafybeib3bzis4mejzsnzsb65od3rnv5ffit7vsllratddjkgfgq4wiamqu.on.fleek.co
regexFilter = `${commonStaticUrlStart}(.*?)${commonStaticUrlEnd}`
regexSubstitution = redirectUrl
.replace(subdomainPart as string, '\\1') // replace CID
.replace(new RegExp(`${originURL.pathname}?$`), '\\2') // replace path

break
}
Copy link
Member

Choose a reason for hiding this comment

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

💭 Unsure if i got this right, but iirc this naive catch-all is not something we've supported before, and may lead to false-positives if someone has non-ipfs domain that happens to be a valid CID.

Domains like f01550000.example.com should not get redirected.
We have subdomain gateway specification for a reason (to remove false-positives like this).

My initial suggestion would be to remove this (we don't want to keep list of "blessed" services, and services should follow specs if they want to get protocol upgrade: subdomain convention, or X-Ipfs-Path in response)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand the concern, fleek for example is one of those services, we only add this rule if the old code evaluates this redirect i.e. origin url and destination url are modified.

We can discuss that as a follow-up issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comment #1253 (comment) clarifies the issue bit more, I'll remove this check.

Copy link
Member

@lidel lidel Aug 25, 2023

Choose a reason for hiding this comment

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

Yes, if we dont know the namespace, we can't make informed decision about the intention behind the CID, so better to not introduce "guesswork".
Subdomain gateway convention was created for this very reason, making detection deterministic, no ambiguity.
(For example: if you have CID with libp2p-key codec, should it fetch the Public key, or resolve IPNS?)

if (DEFAULT_NAMESPACES.has(subdomainPart as string)) {
// We found a namespace, this is going to match group 2, i.e. namespace.
// e.g https://bafybeib3bzis4mejzsnzsb65od3rnv5ffit7vsllratddjkgfgq4wiamqu.ipfs.dweb.link
regexFilter = `${commonStaticUrlStart}(.*?)\\.(${[...DEFAULT_NAMESPACES].join('|')})${commonStaticUrlEnd}`

regexSubstitution = redirectUrl
.replace(subdomain.reverse().join('.'), '\\1') // replace subdomain or CID.
.replace(`/${subdomainPart as string}/`, '/\\2/') // replace namespace dynamically.

const pathWithSearch = originURL.pathname + originURL.search
if (pathWithSearch !== '/') {
regexSubstitution = regexSubstitution.replace(pathWithSearch, '/\\3') // replace path
} else {
regexSubstitution += '\\3'
}

break
}
// till we find a namespace or CID, we keep adding subdomains to the staticUrl.
staticUrl.unshift(subdomainPart as string)
}
Copy link
Member

Choose a reason for hiding this comment

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

can we split this big while chunk out into another function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed: #1261


if (regexFilter !== origRegexFilter) {
// we found a valid regexFilter, so we can return.
return { regexSubstitution, regexFilter }
Copy link
Member

Choose a reason for hiding this comment

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

i'm unsure how regexFilter !== origRegexFilter confirms that we have a valid regexFilter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in #1261

} else {
// we didn't find a valid regexFilter, so we can return the default.
regexFilter = originUrl
}
}

// if the namespaces are the same, we can generate simpler regex.
// The only value that needs special handling is the `uri` param.
// TODO: Remove this check, as `uri` param is deprecated.
Copy link
Member

Choose a reason for hiding this comment

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

💭 this is new to me – where can I read more about this deprecation? or is it about deprecation of use in companion for something (if so, needs clarification/rephrasing)

(afaik ?uri= is part of https://specs.ipfs.tech/http-gateways/subdomain-gateway/#uri-request-query-parameter and not going anywhere, as long navigator.registerProtocolHandler exists)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am sorry about this, this comment is irrelevant (only the comment, the code is still good)

The reason I got that impression was:

https://dweb.link/ipfs/?uri=ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest

shows me:

Screenshot 2023-08-15 at 10 07 07 PM

After companion redirects it correctly to: http://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi.ipfs.localhost:8080/?argTest#hashTest

I see the correct cat:

Screenshot 2023-08-15 at 10 09 18 PM

will remove this comment, apologies and good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
// TODO: Remove this check, as `uri` param is deprecated.

Copy link
Member

@lidel lidel Aug 25, 2023

Choose a reason for hiding this comment

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

Ah, interesting edge case. The 422 error on https://dweb.link/ipfs/?uri=ipfs%3A%2F%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%3FargTest%23hashTest is a bug in nginx config somewhere in our infra, we track it n https://github.com/protocol/bifrost-infra/issues/2604

?uri is not deprecated, 👍 to remove this TODO

if (
DEFAULT_NAMESPACES.has(originNS) &&
DEFAULT_NAMESPACES.has(redirectNS) &&
originNS === redirectNS &&
originURL.searchParams.get('uri') == null
) {
Copy link
Member

Choose a reason for hiding this comment

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

can we name this check? what does the aggregate of all these statements mean? i.e.

const hasSameRedirectAndOriginNamespace = hasSameNamespace(originURL, originNS, redirectNS)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in #1261

// A redirect like
// https://ipfs.io/ipfs/QmZMxU -> http://localhost:8080/ipfs/QmZMxU
const [originFirst, originLast] = originUrl.split(`/${originNS}/`)
const defaultNSRegexStr = `(${[...DEFAULT_NAMESPACES].join('|')})`
regexFilter = `^${escapeURLRegex(originFirst)}\\/${defaultNSRegexStr}\\/${RULE_REGEX_ENDING}`
.replace(/https?/ig, 'https?')
regexSubstitution = redirectUrl
.replace(`/${redirectNS}/`, '/\\1/')
.replace(originLast, '\\2')
return { regexSubstitution, regexFilter }
Copy link
Member

Choose a reason for hiding this comment

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

It would be great to encapsulate the different return values throughout this function so we could name them and break up this function a bit more.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in #1261

}

// We can traverse the URL from the end, and find the first character that is different.
let commonIdx = 1
while (commonIdx < Math.min(originUrl.length, redirectUrl.length)) {
Expand All @@ -129,12 +222,10 @@ function constructRegexFilter ({ originUrl, redirectUrl }: redirectHandlerInput)
}

// We can now construct the regex filter and substitution.
let regexSubstitution = redirectUrl.slice(0, redirectUrl.length - commonIdx + 1) + '\\1'
regexSubstitution = redirectUrl.slice(0, redirectUrl.length - commonIdx + 1) + '\\1'
// We need to escape the characters that are allowed in the URL, but not in the regex.
const regexFilterFirst = escapeURLRegex(originUrl.slice(0, originUrl.length - commonIdx + 1))
// We need to match the rest of the URL, so we can use a wildcard.
const RULE_REGEX_ENDING = '((?:[^\\.]|$).*)$'
let regexFilter = `^${regexFilterFirst}${RULE_REGEX_ENDING}`.replace(/https?/ig, 'https?')
regexFilter = `^${regexFilterFirst}${RULE_REGEX_ENDING}`.replace(/https?/ig, 'https?')

// This method does not parse:
// originUrl: "https://awesome.ipfs.io/"
Expand Down Expand Up @@ -247,11 +338,11 @@ async function reconcileRulesAndRemoveOld (state: CompanionState): Promise<void>
}
}

const { port } = new URL(state.gwURLString)
// make sure that the default rules are added.
for (const { originUrl, redirectUrl } of DEFAULT_LOCAL_RULES) {
const { port } = new URL(state.gwURLString)
const regexFilter = `^${escapeURLRegex(`${originUrl}:${port}`)}(.*)$`
const regexSubstitution = `${redirectUrl}:${port}\\1`
const regexFilter = `^${escapeURLRegex(`${originUrl}:${port}`)}${RULE_REGEX_ENDING}`
const regexSubstitution = `${redirectUrl}:${port}/\\1`

if (!savedRegexFilters.has(regexFilter)) {
// We need to add the new rule.
Expand Down
3 changes: 3 additions & 0 deletions add-on/src/types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'is-ipfs' {
Copy link
Member

Choose a reason for hiding this comment

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

function cid (value: string): boolean
}
26 changes: 13 additions & 13 deletions test/functional/lib/ipfs-request-dnslink.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io\\/index\\.html',
destination: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd`
destination: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd\\1`
}
})
})
Expand Down Expand Up @@ -174,7 +174,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -191,7 +191,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -208,7 +208,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -229,7 +229,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand Down Expand Up @@ -282,7 +282,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -304,7 +304,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io\\/index\\.html',
destination: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd`
destination: `${activeGateway}/ipfs/QmbfimSwTuCvGL8XBr3yk1iCjqgk2co2n21cWmcQohymDd\\1`
}
})
})
Expand Down Expand Up @@ -335,7 +335,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -357,7 +357,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -377,7 +377,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand Down Expand Up @@ -408,7 +408,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -425,7 +425,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand All @@ -442,7 +442,7 @@ describe('modifyRequest processing of DNSLinks', function () {
MV2Expectation: `${activeGateway}/ipns/explore.ipld.io/index.html?argTest#hashTest`,
MV3Expectation: {
origin: '^https?\\:\\/\\/explore\\.ipld\\.io',
destination: `${activeGateway}/ipns/explore.ipld.io`
destination: `${activeGateway}/ipns/explore.ipld.io\\1`
}
})
})
Expand Down
Loading