Add AnyClip integration tools and extracted source code
- Add authentication scripts with SubtleCrypto password encryption - Add sourcemap extraction pipeline (update-urls, download-sourcemaps, extract-sources) - Add Playwright API interception script for monetization endpoints - Document two-step auth flow with JWT tokens and dual cookies - Move extracted source from root to anyclip/ directory - Add project configuration (.env.example, .gitignore, CLAUDE.md)
This commit is contained in:
138
anyclip/client/resolve-href.ts
Normal file
138
anyclip/client/resolve-href.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import type { NextRouter, Url } from '../shared/lib/router/router'
|
||||
|
||||
import { searchParamsToUrlQuery } from '../shared/lib/router/utils/querystring'
|
||||
import { formatWithValidation } from '../shared/lib/router/utils/format-url'
|
||||
import { omit } from '../shared/lib/router/utils/omit'
|
||||
import { normalizeRepeatedSlashes } from '../shared/lib/utils'
|
||||
import { normalizePathTrailingSlash } from './normalize-trailing-slash'
|
||||
import { isLocalURL } from '../shared/lib/router/utils/is-local-url'
|
||||
import { isDynamicRoute } from '../shared/lib/router/utils'
|
||||
import { interpolateAs } from '../shared/lib/router/utils/interpolate-as'
|
||||
import { getRouteRegex } from '../shared/lib/router/utils/route-regex'
|
||||
import { getRouteMatcher } from '../shared/lib/router/utils/route-matcher'
|
||||
|
||||
/**
|
||||
* Resolves a given hyperlink with a certain router state (basePath not included).
|
||||
* Preserves absolute urls.
|
||||
*/
|
||||
export function resolveHref(
|
||||
router: NextRouter,
|
||||
href: Url,
|
||||
resolveAs: true
|
||||
): [string, string] | [string]
|
||||
export function resolveHref(
|
||||
router: NextRouter,
|
||||
href: Url,
|
||||
resolveAs?: false
|
||||
): string
|
||||
export function resolveHref(
|
||||
router: NextRouter,
|
||||
href: Url,
|
||||
resolveAs?: boolean
|
||||
): [string, string] | [string] | string {
|
||||
// we use a dummy base url for relative urls
|
||||
let base: URL
|
||||
let urlAsString = typeof href === 'string' ? href : formatWithValidation(href)
|
||||
|
||||
// repeated slashes and backslashes in the URL are considered
|
||||
// invalid and will never match a Next.js page/file
|
||||
// https://www.rfc-editor.org/rfc/rfc3986.html#section-3.1
|
||||
const urlProtoMatch = urlAsString.match(/^[a-z][a-z0-9+.-]*:\/\//i)
|
||||
const urlAsStringNoProto = urlProtoMatch
|
||||
? urlAsString.slice(urlProtoMatch[0].length)
|
||||
: urlAsString
|
||||
|
||||
const urlParts = urlAsStringNoProto.split('?', 1)
|
||||
|
||||
if ((urlParts[0] || '').match(/(\/\/|\\)/)) {
|
||||
console.error(
|
||||
`Invalid href '${urlAsString}' passed to next/router in page: '${router.pathname}'. Repeated forward-slashes (//) or backslashes \\ are not valid in the href.`
|
||||
)
|
||||
const normalizedUrl = normalizeRepeatedSlashes(urlAsStringNoProto)
|
||||
urlAsString = (urlProtoMatch ? urlProtoMatch[0] : '') + normalizedUrl
|
||||
}
|
||||
|
||||
// Return because it cannot be routed by the Next.js router
|
||||
if (!isLocalURL(urlAsString)) {
|
||||
return (resolveAs ? [urlAsString] : urlAsString) as string
|
||||
}
|
||||
|
||||
try {
|
||||
let baseBase = urlAsString.startsWith('#') ? router.asPath : router.pathname
|
||||
|
||||
// If the provided href is only a query string, it is safer to use the asPath
|
||||
// considering rewrites.
|
||||
if (urlAsString.startsWith('?')) {
|
||||
baseBase = router.asPath
|
||||
|
||||
// However, if is a dynamic route, we need to use the pathname to preserve the
|
||||
// query interpolation and rewrites (router.pathname will look like "/[slug]").
|
||||
if (isDynamicRoute(router.pathname)) {
|
||||
baseBase = router.pathname
|
||||
|
||||
const routeRegex = getRouteRegex(router.pathname)
|
||||
const match = getRouteMatcher(routeRegex)(router.asPath)
|
||||
|
||||
// For dynamic routes, if asPath doesn't match the pathname regex, it is a rewritten path.
|
||||
// In this case, should use asPath to preserve the current URL.
|
||||
if (!match) {
|
||||
baseBase = router.asPath
|
||||
}
|
||||
|
||||
// Note: There is an edge case where the pathname is dynamic, and also a rewrite path to the same segment.
|
||||
// E.g. in "/[slug]" path, rewrite "/foo" -> "/bar"
|
||||
|
||||
// In this case, it will be treated as a non-rewritten path and possibly interpolate the query string.
|
||||
// E.g., "/any?slug=foo" will become the content of "/foo", not rewritten as "/bar"
|
||||
|
||||
// This is currently a trade-off of not resolving rewrite paths on every Router/Link call,
|
||||
// but using a lighter route regex pattern check.
|
||||
}
|
||||
}
|
||||
|
||||
base = new URL(baseBase, 'http://n')
|
||||
} catch (_) {
|
||||
// fallback to / for invalid asPath values e.g. //
|
||||
base = new URL('/', 'http://n')
|
||||
}
|
||||
|
||||
try {
|
||||
const finalUrl = new URL(urlAsString, base)
|
||||
finalUrl.pathname = normalizePathTrailingSlash(finalUrl.pathname)
|
||||
let interpolatedAs = ''
|
||||
|
||||
if (
|
||||
isDynamicRoute(finalUrl.pathname) &&
|
||||
finalUrl.searchParams &&
|
||||
resolveAs
|
||||
) {
|
||||
const query = searchParamsToUrlQuery(finalUrl.searchParams)
|
||||
|
||||
const { result, params } = interpolateAs(
|
||||
finalUrl.pathname,
|
||||
finalUrl.pathname,
|
||||
query
|
||||
)
|
||||
|
||||
if (result) {
|
||||
interpolatedAs = formatWithValidation({
|
||||
pathname: result,
|
||||
hash: finalUrl.hash,
|
||||
query: omit(query, params),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// if the origin didn't change, it means we received a relative href
|
||||
const resolvedHref =
|
||||
finalUrl.origin === base.origin
|
||||
? finalUrl.href.slice(finalUrl.origin.length)
|
||||
: finalUrl.href
|
||||
|
||||
return resolveAs
|
||||
? [resolvedHref, interpolatedAs || resolvedHref]
|
||||
: resolvedHref
|
||||
} catch (_) {
|
||||
return resolveAs ? [urlAsString] : urlAsString
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user