diff --git a/binderhub/static/js/index.js b/binderhub/static/js/index.js index 40ceac73b..57ee1f436 100644 --- a/binderhub/static/js/index.js +++ b/binderhub/static/js/index.js @@ -3,11 +3,10 @@ import ClipboardJS from "clipboard"; import "event-source-polyfill"; -import { BinderRepository } from "@jupyterhub/binderhub-client"; +import { BinderRepository, detect } from "@jupyterhub/binderhub-client"; import { updatePathText } from "./src/path"; import { nextHelpText } from "./src/loading"; import { updateFavicon } from "./src/favicon"; -import { detect } from "./src/autodetect"; import "xterm/css/xterm.css"; @@ -175,28 +174,33 @@ function indexMain() { ?.getData("text") .trim(); if (pastedText) { - const fields = detect(pastedText); - console.log(fields); - if (fields) { - $("#provider_prefix-selected").text(fields.display_name); - $("#provider_prefix").val(fields.provider); - $("#repository").val(fields.repo); - if (fields.ref) { - $("#ref").val(fields.ref); + event.preventDefault(); + detect(BASE_URL, pastedText).then((fields) => { + console.log(fields); + if (fields) { + $("#provider_prefix-selected").text(fields.displayName); + $("#provider_prefix").val(fields.providerPrefix); + $("#repository").val(fields.repository); + if (fields.ref) { + $("#ref").val(fields.ref); + } + if (fields.path) { + $("#filepath").val(fields.path); + $("#url-or-file-selected").text( + fields.pathType === "filepath" ? "File" : "URL", + ); + } + updatePathText(); + // updateRepoText(BASE_URL); + } else { + // else autodetect failed, paste the original + $("#repository").val(pastedText); } - if (fields.path) { - $("#filepath").val(fields.path); - $("#url-or-file-selected").text( - fields.pathType === "filepath" ? "File" : "URL", - ); - } - updatePathText(); - event.preventDefault(); - updateRepoText(BASE_URL); - } + updateUrls(BADGE_BASE_URL); + }); + } else { + updateUrls(BADGE_BASE_URL); } - // else autodetect failed, let the paste go through - updateUrls(BADGE_BASE_URL); }); $("#repository").on("keyup change", function () { diff --git a/binderhub/static/js/src/autodetect.js b/binderhub/static/js/src/autodetect.js deleted file mode 100644 index 365f961e5..000000000 --- a/binderhub/static/js/src/autodetect.js +++ /dev/null @@ -1,27 +0,0 @@ -import { getConfigDict } from "./repo"; - -export function detect(text) { - // Assumes configDict was already loaded by another module - const configDict = getConfigDict(); - for (const provider in configDict) { - const regex_detect = configDict[provider].regex_detect || []; - for (const regex of regex_detect) { - const m = text.match(regex); - if (m?.groups.repo) { - return { - provider: provider, - display_name: configDict[provider].display_name, - repo: m.groups.repo, - ref: m.groups.ref, - path: m.groups.filepath || m.groups.urlpath || null, - pathType: m.groups.filepath - ? "filepath" - : m.groups.urlpath - ? "urlpath" - : null, - }; - } - } - } - return null; -} diff --git a/binderhub/static/js/src/repo.js b/binderhub/static/js/src/repo.js index 5bb701506..a6a0e3c81 100644 --- a/binderhub/static/js/src/repo.js +++ b/binderhub/static/js/src/repo.js @@ -1,9 +1,9 @@ +import { getRepoProviders } from "@jupyterhub/binderhub-client"; + /** - * Dict holding cached values of API request to _config endpoint + * @param {Object} configDict Dict holding cached values of API request to _config endpoint */ -let configDict = {}; - -function setLabels() { +function setLabels(configDict) { const provider = $("#provider_prefix").val(); const text = configDict[provider]["text"]; const tagText = configDict[provider]["tag_text"]; @@ -23,19 +23,5 @@ function setLabels() { * @param {URL} baseUrl Base URL to use for constructing path to _config endpoint */ export function updateRepoText(baseUrl) { - if (Object.keys(configDict).length === 0) { - const configUrl = new URL("_config", baseUrl); - fetch(configUrl).then((resp) => { - resp.json().then((data) => { - configDict = data; - setLabels(); - }); - }); - } else { - setLabels(); - } -} - -export function getConfigDict() { - return configDict; + getRepoProviders(baseUrl).then(setLabels); } diff --git a/js/packages/binderhub-client/lib/autodetect.js b/js/packages/binderhub-client/lib/autodetect.js new file mode 100644 index 000000000..45431f102 --- /dev/null +++ b/js/packages/binderhub-client/lib/autodetect.js @@ -0,0 +1,67 @@ +import { fetch as fetchPolyfill } from "whatwg-fetch"; + +// Use native browser fetch if available, and use the polyfill if not available +// (e.g. in tests https://github.com/jestjs/jest/issues/13834#issuecomment-1407375787) +// @todo: this is only a problem in the jest tests, so get rid of this and mock fetch instead +const fetch = window.fetch || fetchPolyfill; + +/** + * Dict holding cached values of API request to _config endpoint + */ +let repoProviders = {}; + +/** + * Get the repo provider configurations supported by the BinderHub instance + * + * @param {URL} baseUrl Base URL to use for constructing path to _config endpoint + */ +export async function getRepoProviders(baseUrl) { + if (Object.keys(repoProviders).length === 0) { + const configUrl = new URL("_config", baseUrl); + const resp = await fetch(configUrl); + repoProviders = resp.json(); + } + return repoProviders; +} + +/** + * Attempt to parse a string (typically a repository URL) into a BinderHub + * provider/repository/reference/path + * + * @param {URL} baseUrl Base URL to use for constructing path to _config endpoint + * @param {string} text Repository URL or similar to parse + * @returns {Object} An object if the repository could be parsed with fields + * - providerPrefix Prefix denoting what provider was selected + * - repository Repository to build + * - ref Ref in this repo to build (optional) + * - path Path to launch after this repo has been built (optional) + * - pathType Type of thing to open path with (raw url, notebook file) (optional) + * - providerName User friendly display name of the provider (optional) + * null otherwise + */ +export async function detect(baseUrl, text) { + const config = await getRepoProviders(baseUrl); + + for (const provider in config) { + const regex_detect = config[provider].regex_detect || []; + for (const regex of regex_detect) { + const m = text.match(regex); + if (m?.groups.repo) { + return { + providerPrefix: provider, + repository: m.groups.repo, + ref: m.groups.ref, + path: m.groups.filepath || m.groups.urlpath || null, + pathType: m.groups.filepath + ? "filepath" + : m.groups.urlpath + ? "urlpath" + : null, + providerName: config[provider].display_name, + }; + } + } + } + + return null; +} diff --git a/js/packages/binderhub-client/lib/index.js b/js/packages/binderhub-client/lib/index.js index 9f602494c..e69df49c0 100644 --- a/js/packages/binderhub-client/lib/index.js +++ b/js/packages/binderhub-client/lib/index.js @@ -1,6 +1,8 @@ import { NativeEventSource, EventSourcePolyfill } from "event-source-polyfill"; import { EventIterator } from "event-iterator"; +import { detect, getRepoProviders } from "./autodetect"; + // Use native browser EventSource if available, and use the polyfill if not available const EventSource = NativeEventSource || EventSourcePolyfill; @@ -211,3 +213,5 @@ export function makeBadgeMarkup(publicBaseUrl, url, syntax) { ); } } + +export { detect, getRepoProviders }; diff --git a/js/packages/binderhub-client/package.json b/js/packages/binderhub-client/package.json index b2ab42c3c..089c12986 100644 --- a/js/packages/binderhub-client/package.json +++ b/js/packages/binderhub-client/package.json @@ -14,7 +14,8 @@ }, "homepage": "https://github.com/jupyterhub/binderhub#readme", "dependencies": { + "event-iterator": "^2.0.0", "event-source-polyfill": "^1.0.31", - "event-iterator": "^2.0.0" + "whatwg-fetch": "^3.6.19" } }