Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"chalk": "^2.4.2",
"express": "^4.17.0",
"global-agent": "^2.1.5",
"got": "^9.6.0",
"memory-cache": "^0.2.0",
Expand All @@ -62,7 +63,6 @@
"babel-jest": "^24.9.0",
"codecov": "^3.6.1",
"core-js": "^3.3.4",
"express": "^4.17.0",
"jest": "^24.9.0",
"lodash": "4.17.15",
"nodemon": "^1.18.10",
Expand All @@ -80,7 +80,6 @@
"verdaccio-htpasswd": "^8.2.0"
},
"peerDependencies": {
"express": "4",
"lodash": "4",
"verdaccio": "3 || 4"
},
Expand Down
6 changes: 5 additions & 1 deletion src/client/plugin/usage-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ export function getUsageInfo() {
return "Click the login button to authenticate with GitHub."
}

const configBase = "//" + location.host + location.pathname
const configBase = (window as any).VERDACCIO_API_URL
? (window as any).VERDACCIO_API_URL
.replace(/^https?:/, "")
.replace(/-\/verdaccio\/$/, "")
: `//${location.host}${location.pathname}`
const authToken = localStorage.getItem("npm")
return [
`npm config set ${configBase}:_authToken "${authToken}"`,
Expand Down
81 changes: 55 additions & 26 deletions src/client/verdaccio-4.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getUsageInfo, init, isLoggedIn } from "./plugin"

const usageInfoSelector = "#help-card .MuiCardContent-root span, .MuiDialogContent-root .MuiTypography-root span"
const randomId = "Os1waV6BSoZQKfFwNlIwS"
const helpCardUsageInfoSelector = "#help-card .MuiCardContent-root span"
const dialogUsageInfoSelector = "#registryInfo--dialog-container .MuiDialogContent-root .MuiTypography-root span"
const randomClass = "Os1waV6BSoZQKfFwNlIwS"

// copied from here as it needs to be the same behaviour
// https://github.com/verdaccio/ui/blob/master/src/utils/cli-utils.ts
Expand All @@ -18,39 +19,67 @@ export function copyToClipboard(text: string) {
document.body.removeChild(node)
}

function markUsageInfoNodes() {
document.querySelectorAll(usageInfoSelector).forEach(node => {
const infoEl = node as HTMLSpanElement

if (infoEl.innerText.includes("adduser")) {
infoEl.classList.add(randomId)
}
})
}

function modifyUsageInfoNodes() {
function modifyUsageInfoNodes(
selector: string,
findPredicate: (node: HTMLElement) => boolean,
): void {
const usageInfo = getUsageInfo()
const loggedIn = isLoggedIn()

document.querySelectorAll("." + randomId).forEach(node => {
const infoEl = node as HTMLSpanElement
const parentEl = infoEl.parentElement as HTMLDivElement
const copyEl = parentEl.querySelector("button") as HTMLButtonElement
const infoElements: NodeListOf<HTMLSpanElement> = document.querySelectorAll(selector)
const firstUsageInfoEl = Array.prototype.find.call(infoElements, findPredicate)
const hasInjectedElement = !!Array.prototype.find.call(
infoElements,
(node: HTMLElement) => node.parentElement!.classList.contains(randomClass),
)

// We can't find any element related to usage instructions,
// or we have already injected elements
if (!firstUsageInfoEl || hasInjectedElement) {
return
}

const cachedParent: HTMLDivElement | null = firstUsageInfoEl.parentElement
if (cachedParent) {
usageInfo.split("\n").reverse().forEach(info => {
const clonedNode = cachedParent.cloneNode(true) as HTMLDivElement
const textElem = clonedNode.querySelector("span")!
const copyEl = clonedNode.querySelector("button")!

clonedNode.classList.add(randomClass)
textElem.innerText = info
copyEl.style.visibility = loggedIn ? "visible" : "hidden"
copyEl.onclick = e => {
e.preventDefault()
e.stopPropagation()
copyToClipboard(info)
}

infoEl.innerText = usageInfo
cachedParent.insertAdjacentElement("afterend", clonedNode)
})
}

copyEl.style.visibility = loggedIn ? "visible" : "hidden"
copyEl.onclick = e => {
e.preventDefault()
e.stopPropagation()
copyToClipboard(usageInfo)
infoElements.forEach((node => {
if (
// We only match lines related to bundler commands
!!node.innerText.match(/^(npm|pnpm|yarn)/) &&
// And only commands that we want to remove
(node.innerText.includes("adduser") || node.innerText.includes("set password"))
) {
node.parentElement!.parentElement!.removeChild(node.parentElement!)
}
})
}))
}

function updateUsageInfo() {
markUsageInfoNodes()
modifyUsageInfoNodes()
modifyUsageInfoNodes(helpCardUsageInfoSelector, node => node.innerText.includes("adduser"))
modifyUsageInfoNodes(
dialogUsageInfoSelector,
node => !!node.innerText.match(
// This checks for an element showing instructions to set the registry URL
/((npm|pnpm) set|(yarn) config set)/,
)
)
}

init({
Expand Down
2 changes: 1 addition & 1 deletion src/server/flows/WebFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class WebFlow implements IPluginMiddleware<any> {
* We issue a JWT using these values and pass them back to the frontend as
* query parameters so they can be stored in the browser.
*
* The username and token are encryped and base64 encoded to form a token for
* The username and token are encrypted and base64 encoded to form a token for
* the npm CLI.
*
* There is no need to later decode and decrypt the token. This process is
Expand Down