react-cheminfo 0.57.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chart/ui/ChartTrackingLayer.js +3 -1
- package/lib/chart/ui/ChartTrackingLayer.js.map +1 -1
- package/lib/chart/ui/useChartSize.d.ts +16 -0
- package/lib/chart/ui/useChartSize.d.ts.map +1 -1
- package/lib/chart/ui/useChartSize.js +34 -1
- package/lib/chart/ui/useChartSize.js.map +1 -1
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/core/unlistedSites.d.ts.map +1 -1
- package/lib/ecosystem/core/unlistedSites.js +19 -3
- package/lib/ecosystem/core/unlistedSites.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +4 -1
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/locales/de.json +26 -1
- package/lib/locales/en.json +26 -1
- package/lib/locales/es.json +26 -1
- package/lib/locales/fr.json +26 -1
- package/lib/locales/it.json +26 -1
- package/lib/moleculesToken/core/index.d.ts +3 -0
- package/lib/moleculesToken/core/index.d.ts.map +1 -0
- package/lib/moleculesToken/core/index.js +2 -0
- package/lib/moleculesToken/core/index.js.map +1 -0
- package/lib/moleculesToken/core/moleculesToken.d.ts +101 -0
- package/lib/moleculesToken/core/moleculesToken.d.ts.map +1 -0
- package/lib/moleculesToken/core/moleculesToken.js +137 -0
- package/lib/moleculesToken/core/moleculesToken.js.map +1 -0
- package/lib/moleculesToken/ui/MoleculesTokenTable.d.ts +32 -0
- package/lib/moleculesToken/ui/MoleculesTokenTable.d.ts.map +1 -0
- package/lib/moleculesToken/ui/MoleculesTokenTable.js +124 -0
- package/lib/moleculesToken/ui/MoleculesTokenTable.js.map +1 -0
- package/lib/moleculesToken/ui/index.d.ts +3 -0
- package/lib/moleculesToken/ui/index.d.ts.map +1 -0
- package/lib/moleculesToken/ui/index.js +2 -0
- package/lib/moleculesToken/ui/index.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/chart/ui/ChartTrackingLayer.tsx +3 -1
- package/src/chart/ui/useChartSize.ts +44 -1
- package/src/core.ts +1 -0
- package/src/ecosystem/core/types.ts +3 -2
- package/src/ecosystem/core/unlistedSites.ts +19 -3
- package/src/ecosystem/ui/glyphs.tsx +11 -1
- package/src/locales/de.json +26 -1
- package/src/locales/en.json +26 -1
- package/src/locales/es.json +26 -1
- package/src/locales/fr.json +26 -1
- package/src/locales/it.json +26 -1
- package/src/moleculesToken/core/index.ts +11 -0
- package/src/moleculesToken/core/moleculesToken.ts +256 -0
- package/src/moleculesToken/ui/MoleculesTokenTable.tsx +249 -0
- package/src/moleculesToken/ui/index.ts +2 -0
- package/src/ui.ts +1 -0
- package/styles/chrome.css +35 -0
|
@@ -19,9 +19,11 @@ export const UNLISTED_SITES: readonly EcosystemSite[] = [
|
|
|
19
19
|
mark: { plate: '#be185d', accent: '#5eead4' },
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
id: '
|
|
23
|
-
name
|
|
24
|
-
|
|
22
|
+
id: 'chembio',
|
|
23
|
+
// The name splits on itself, chemistry and biology, so it carries no
|
|
24
|
+
// domain.
|
|
25
|
+
name: { lead: 'chem', alt: 'bio', dot: false },
|
|
26
|
+
host: 'chembio.cheminfo.org',
|
|
25
27
|
tagline: 'Natural products, and the organisms they were found in.',
|
|
26
28
|
group: 'research',
|
|
27
29
|
// Moss, the green of the plants most natural products come from, 8.5
|
|
@@ -50,4 +52,18 @@ export const UNLISTED_SITES: readonly EcosystemSite[] = [
|
|
|
50
52
|
// so the link row of the mark takes a lighter blue of the same hue.
|
|
51
53
|
mark: { plate: '#5c4033', accent: '#6b9bf2' },
|
|
52
54
|
},
|
|
55
|
+
{
|
|
56
|
+
id: 'mz',
|
|
57
|
+
name: { lead: 'mz', alt: 'cheminfo', dot: true },
|
|
58
|
+
host: 'mz.cheminfo.org',
|
|
59
|
+
tagline: 'Mass spectra, classified by how they were measured.',
|
|
60
|
+
group: 'spectra',
|
|
61
|
+
// Ultramarine, the deep blue of a dye whose colour is its spectrum.
|
|
62
|
+
brand: '#2200c4',
|
|
63
|
+
// An orange answers it at 5.2:1 on white, for the name.
|
|
64
|
+
brandAlt: '#c2410c',
|
|
65
|
+
// The mark takes a brighter orange, so the base peak stands apart from
|
|
66
|
+
// the ultramarine plate at 16 px.
|
|
67
|
+
mark: { plate: '#2200c4', accent: '#f97316' },
|
|
68
|
+
},
|
|
53
69
|
];
|
|
@@ -509,7 +509,7 @@ export const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
|
|
|
509
509
|
// A ring, the compound, with a stem leading out of it to a leaf: a natural
|
|
510
510
|
// product, and the organism it came from. The leaf carries the answering
|
|
511
511
|
// colour.
|
|
512
|
-
|
|
512
|
+
chembio: (alt) => (
|
|
513
513
|
<>
|
|
514
514
|
<path
|
|
515
515
|
d="M11.5 14 16.7 17 16.7 23 11.5 26 6.3 23 6.3 17Z"
|
|
@@ -530,6 +530,16 @@ export const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
|
|
|
530
530
|
/>
|
|
531
531
|
</>
|
|
532
532
|
),
|
|
533
|
+
// A mass spectrum: three peaks on a baseline, the tallest, the base peak, in
|
|
534
|
+
// the answering colour. Every edge falls on a whole pixel at 16 px.
|
|
535
|
+
mz: (alt) => (
|
|
536
|
+
<>
|
|
537
|
+
<rect x="4" y="26" width="24" height="2" fill="#ffffff" />
|
|
538
|
+
<rect x="6" y="16" width="4" height="10" rx="1" fill="#ffffff" />
|
|
539
|
+
<rect x="14" y="6" width="4" height="20" rx="1" fill={alt} />
|
|
540
|
+
<rect x="22" y="12" width="4" height="14" rx="1" fill="#ffffff" />
|
|
541
|
+
</>
|
|
542
|
+
),
|
|
533
543
|
// An infobox: the structure at its top, a ring, and two rows of data under
|
|
534
544
|
// it. The second row carries the answering colour, the blue of a link. Both
|
|
535
545
|
// rows and the gap between them fall on whole pixels at 16 px.
|
package/src/locales/de.json
CHANGED
|
@@ -593,5 +593,30 @@
|
|
|
593
593
|
"taxonomy.rank.noRank": "ohne Rang",
|
|
594
594
|
"taxonomy.rank.clade": "Klade",
|
|
595
595
|
"taxonomy.rank.cellularRoot": "zelluläre Wurzel",
|
|
596
|
-
"taxonomy.rank.acellularRoot": "azelluläre Wurzel"
|
|
596
|
+
"taxonomy.rank.acellularRoot": "azelluläre Wurzel",
|
|
597
|
+
"moleculesToken.title": "Tokens für molecules.cheminfo.org",
|
|
598
|
+
"moleculesToken.checkedAt": "Geprüft am {when}",
|
|
599
|
+
"moleculesToken.checkAgain": "Erneut prüfen",
|
|
600
|
+
"moleculesToken.notChecked": "Noch nicht geprüft: Der Dienst, der in molecules.cheminfo.org schreibt, prüft seine Tokens beim Start.",
|
|
601
|
+
"moleculesToken.column.variable": "Variable",
|
|
602
|
+
"moleculesToken.column.source": "Quelle",
|
|
603
|
+
"moleculesToken.column.status": "Status",
|
|
604
|
+
"moleculesToken.column.details": "Details",
|
|
605
|
+
"moleculesToken.anySource": "beliebige Quelle",
|
|
606
|
+
"moleculesToken.verdict.ok": "gültig",
|
|
607
|
+
"moleculesToken.verdict.otherSource": "einer anderen Quelle",
|
|
608
|
+
"moleculesToken.verdict.refused": "abgelehnt",
|
|
609
|
+
"moleculesToken.verdict.unset": "nicht gesetzt",
|
|
610
|
+
"moleculesToken.verdict.uncheckable": "nicht prüfbar",
|
|
611
|
+
"moleculesToken.verdict.unanswered": "keine Antwort",
|
|
612
|
+
"moleculesToken.forSource": "für {source}",
|
|
613
|
+
"moleculesToken.issued": "ausgestellt am {when}",
|
|
614
|
+
"moleculesToken.issuedTo": "ausgestellt für {email} am {when}",
|
|
615
|
+
"moleculesToken.lastUsed": "zuletzt verwendet am {when}",
|
|
616
|
+
"moleculesToken.neverUsed": "nie verwendet",
|
|
617
|
+
"moleculesToken.otherSourceDetail": "ausgestellt für {source}: Was er sendet, wird dieser Quelle zugeschrieben",
|
|
618
|
+
"moleculesToken.refusedDetail": "nie ausgestellt oder widerrufen",
|
|
619
|
+
"moleculesToken.unsetDetail": "Setzen Sie {variable} in der .env des Servers.",
|
|
620
|
+
"moleculesToken.uncheckableDetail": "Dieses molecules.cheminfo.org kann noch keine Tokens prüfen.",
|
|
621
|
+
"moleculesToken.howToChange": "Ein Token wird in der .env des Servers geändert und beim Neustart des Dienstes neu gelesen. molecules.cheminfo.org stellt einen neuen aus mit"
|
|
597
622
|
}
|
package/src/locales/en.json
CHANGED
|
@@ -593,5 +593,30 @@
|
|
|
593
593
|
"taxonomy.rank.noRank": "no rank",
|
|
594
594
|
"taxonomy.rank.clade": "clade",
|
|
595
595
|
"taxonomy.rank.cellularRoot": "cellular root",
|
|
596
|
-
"taxonomy.rank.acellularRoot": "acellular root"
|
|
596
|
+
"taxonomy.rank.acellularRoot": "acellular root",
|
|
597
|
+
"moleculesToken.title": "Tokens for molecules.cheminfo.org",
|
|
598
|
+
"moleculesToken.checkedAt": "Checked {when}",
|
|
599
|
+
"moleculesToken.checkAgain": "Check again",
|
|
600
|
+
"moleculesToken.notChecked": "Not checked yet: the service that writes to molecules.cheminfo.org checks its tokens when it starts.",
|
|
601
|
+
"moleculesToken.column.variable": "Variable",
|
|
602
|
+
"moleculesToken.column.source": "Source",
|
|
603
|
+
"moleculesToken.column.status": "Status",
|
|
604
|
+
"moleculesToken.column.details": "Details",
|
|
605
|
+
"moleculesToken.anySource": "any source",
|
|
606
|
+
"moleculesToken.verdict.ok": "valid",
|
|
607
|
+
"moleculesToken.verdict.otherSource": "another source's",
|
|
608
|
+
"moleculesToken.verdict.refused": "refused",
|
|
609
|
+
"moleculesToken.verdict.unset": "not set",
|
|
610
|
+
"moleculesToken.verdict.uncheckable": "cannot be checked",
|
|
611
|
+
"moleculesToken.verdict.unanswered": "no answer",
|
|
612
|
+
"moleculesToken.forSource": "for {source}",
|
|
613
|
+
"moleculesToken.issued": "issued on {when}",
|
|
614
|
+
"moleculesToken.issuedTo": "issued to {email} on {when}",
|
|
615
|
+
"moleculesToken.lastUsed": "last used on {when}",
|
|
616
|
+
"moleculesToken.neverUsed": "never used",
|
|
617
|
+
"moleculesToken.otherSourceDetail": "issued for {source}: what it sends is credited to that source",
|
|
618
|
+
"moleculesToken.refusedDetail": "never issued, or revoked",
|
|
619
|
+
"moleculesToken.unsetDetail": "Set {variable} in the server's .env.",
|
|
620
|
+
"moleculesToken.uncheckableDetail": "This molecules.cheminfo.org cannot check a token yet.",
|
|
621
|
+
"moleculesToken.howToChange": "A token is changed in the server's .env and read again when the service restarts. molecules.cheminfo.org issues a new one with"
|
|
597
622
|
}
|
package/src/locales/es.json
CHANGED
|
@@ -593,5 +593,30 @@
|
|
|
593
593
|
"taxonomy.rank.noRank": "sin rango",
|
|
594
594
|
"taxonomy.rank.clade": "clado",
|
|
595
595
|
"taxonomy.rank.cellularRoot": "raíz celular",
|
|
596
|
-
"taxonomy.rank.acellularRoot": "raíz acelular"
|
|
596
|
+
"taxonomy.rank.acellularRoot": "raíz acelular",
|
|
597
|
+
"moleculesToken.title": "Tokens para molecules.cheminfo.org",
|
|
598
|
+
"moleculesToken.checkedAt": "Comprobados el {when}",
|
|
599
|
+
"moleculesToken.checkAgain": "Comprobar de nuevo",
|
|
600
|
+
"moleculesToken.notChecked": "Aún sin comprobar: el servicio que escribe en molecules.cheminfo.org comprueba sus tokens al arrancar.",
|
|
601
|
+
"moleculesToken.column.variable": "Variable",
|
|
602
|
+
"moleculesToken.column.source": "Fuente",
|
|
603
|
+
"moleculesToken.column.status": "Estado",
|
|
604
|
+
"moleculesToken.column.details": "Detalles",
|
|
605
|
+
"moleculesToken.anySource": "cualquier fuente",
|
|
606
|
+
"moleculesToken.verdict.ok": "válido",
|
|
607
|
+
"moleculesToken.verdict.otherSource": "de otra fuente",
|
|
608
|
+
"moleculesToken.verdict.refused": "rechazado",
|
|
609
|
+
"moleculesToken.verdict.unset": "sin definir",
|
|
610
|
+
"moleculesToken.verdict.uncheckable": "no comprobable",
|
|
611
|
+
"moleculesToken.verdict.unanswered": "sin respuesta",
|
|
612
|
+
"moleculesToken.forSource": "para {source}",
|
|
613
|
+
"moleculesToken.issued": "emitido el {when}",
|
|
614
|
+
"moleculesToken.issuedTo": "emitido para {email} el {when}",
|
|
615
|
+
"moleculesToken.lastUsed": "usado por última vez el {when}",
|
|
616
|
+
"moleculesToken.neverUsed": "nunca usado",
|
|
617
|
+
"moleculesToken.otherSourceDetail": "emitido para {source}: lo que envía se atribuye a esa fuente",
|
|
618
|
+
"moleculesToken.refusedDetail": "nunca emitido, o revocado",
|
|
619
|
+
"moleculesToken.unsetDetail": "Defina {variable} en el .env del servidor.",
|
|
620
|
+
"moleculesToken.uncheckableDetail": "Este molecules.cheminfo.org aún no puede comprobar un token.",
|
|
621
|
+
"moleculesToken.howToChange": "Un token se cambia en el .env del servidor y se vuelve a leer cuando el servicio se reinicia. molecules.cheminfo.org emite uno nuevo con"
|
|
597
622
|
}
|
package/src/locales/fr.json
CHANGED
|
@@ -593,5 +593,30 @@
|
|
|
593
593
|
"taxonomy.rank.noRank": "sans rang",
|
|
594
594
|
"taxonomy.rank.clade": "clade",
|
|
595
595
|
"taxonomy.rank.cellularRoot": "racine cellulaire",
|
|
596
|
-
"taxonomy.rank.acellularRoot": "racine acellulaire"
|
|
596
|
+
"taxonomy.rank.acellularRoot": "racine acellulaire",
|
|
597
|
+
"moleculesToken.title": "Jetons pour molecules.cheminfo.org",
|
|
598
|
+
"moleculesToken.checkedAt": "Vérifiés le {when}",
|
|
599
|
+
"moleculesToken.checkAgain": "Vérifier à nouveau",
|
|
600
|
+
"moleculesToken.notChecked": "Pas encore vérifiés : le service qui écrit dans molecules.cheminfo.org vérifie ses jetons à son démarrage.",
|
|
601
|
+
"moleculesToken.column.variable": "Variable",
|
|
602
|
+
"moleculesToken.column.source": "Source",
|
|
603
|
+
"moleculesToken.column.status": "État",
|
|
604
|
+
"moleculesToken.column.details": "Détails",
|
|
605
|
+
"moleculesToken.anySource": "n'importe quelle source",
|
|
606
|
+
"moleculesToken.verdict.ok": "valide",
|
|
607
|
+
"moleculesToken.verdict.otherSource": "d'une autre source",
|
|
608
|
+
"moleculesToken.verdict.refused": "refusé",
|
|
609
|
+
"moleculesToken.verdict.unset": "non défini",
|
|
610
|
+
"moleculesToken.verdict.uncheckable": "invérifiable",
|
|
611
|
+
"moleculesToken.verdict.unanswered": "pas de réponse",
|
|
612
|
+
"moleculesToken.forSource": "pour {source}",
|
|
613
|
+
"moleculesToken.issued": "émis le {when}",
|
|
614
|
+
"moleculesToken.issuedTo": "émis pour {email} le {when}",
|
|
615
|
+
"moleculesToken.lastUsed": "dernière utilisation le {when}",
|
|
616
|
+
"moleculesToken.neverUsed": "jamais utilisé",
|
|
617
|
+
"moleculesToken.otherSourceDetail": "émis pour {source} : ce qu'il envoie est attribué à cette source",
|
|
618
|
+
"moleculesToken.refusedDetail": "jamais émis, ou révoqué",
|
|
619
|
+
"moleculesToken.unsetDetail": "Définissez {variable} dans le fichier .env du serveur.",
|
|
620
|
+
"moleculesToken.uncheckableDetail": "Ce molecules.cheminfo.org ne sait pas encore vérifier un jeton.",
|
|
621
|
+
"moleculesToken.howToChange": "Un jeton se change dans le fichier .env du serveur, et est relu au redémarrage du service. molecules.cheminfo.org en émet un nouveau avec"
|
|
597
622
|
}
|
package/src/locales/it.json
CHANGED
|
@@ -593,5 +593,30 @@
|
|
|
593
593
|
"taxonomy.rank.noRank": "senza rango",
|
|
594
594
|
"taxonomy.rank.clade": "clade",
|
|
595
595
|
"taxonomy.rank.cellularRoot": "radice cellulare",
|
|
596
|
-
"taxonomy.rank.acellularRoot": "radice acellulare"
|
|
596
|
+
"taxonomy.rank.acellularRoot": "radice acellulare",
|
|
597
|
+
"moleculesToken.title": "Token per molecules.cheminfo.org",
|
|
598
|
+
"moleculesToken.checkedAt": "Verificati il {when}",
|
|
599
|
+
"moleculesToken.checkAgain": "Verifica di nuovo",
|
|
600
|
+
"moleculesToken.notChecked": "Non ancora verificati: il servizio che scrive in molecules.cheminfo.org verifica i suoi token all'avvio.",
|
|
601
|
+
"moleculesToken.column.variable": "Variabile",
|
|
602
|
+
"moleculesToken.column.source": "Fonte",
|
|
603
|
+
"moleculesToken.column.status": "Stato",
|
|
604
|
+
"moleculesToken.column.details": "Dettagli",
|
|
605
|
+
"moleculesToken.anySource": "qualsiasi fonte",
|
|
606
|
+
"moleculesToken.verdict.ok": "valido",
|
|
607
|
+
"moleculesToken.verdict.otherSource": "di un'altra fonte",
|
|
608
|
+
"moleculesToken.verdict.refused": "rifiutato",
|
|
609
|
+
"moleculesToken.verdict.unset": "non impostato",
|
|
610
|
+
"moleculesToken.verdict.uncheckable": "non verificabile",
|
|
611
|
+
"moleculesToken.verdict.unanswered": "nessuna risposta",
|
|
612
|
+
"moleculesToken.forSource": "per {source}",
|
|
613
|
+
"moleculesToken.issued": "emesso il {when}",
|
|
614
|
+
"moleculesToken.issuedTo": "emesso per {email} il {when}",
|
|
615
|
+
"moleculesToken.lastUsed": "usato l'ultima volta il {when}",
|
|
616
|
+
"moleculesToken.neverUsed": "mai usato",
|
|
617
|
+
"moleculesToken.otherSourceDetail": "emesso per {source}: ciò che invia è attribuito a quella fonte",
|
|
618
|
+
"moleculesToken.refusedDetail": "mai emesso, o revocato",
|
|
619
|
+
"moleculesToken.unsetDetail": "Impostare {variable} nel file .env del server.",
|
|
620
|
+
"moleculesToken.uncheckableDetail": "Questo molecules.cheminfo.org non sa ancora verificare un token.",
|
|
621
|
+
"moleculesToken.howToChange": "Un token si cambia nel file .env del server e viene riletto al riavvio del servizio. molecules.cheminfo.org ne emette uno nuovo con"
|
|
597
622
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
CheckMoleculesTokenOptions,
|
|
3
|
+
MoleculesTokenCheck,
|
|
4
|
+
MoleculesTokenToCheck,
|
|
5
|
+
MoleculesTokenVerdict,
|
|
6
|
+
} from './moleculesToken.ts';
|
|
7
|
+
export {
|
|
8
|
+
checkMoleculesToken,
|
|
9
|
+
checkMoleculesTokens,
|
|
10
|
+
publicTokenCheck,
|
|
11
|
+
} from './moleculesToken.ts';
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { trimTrailingSlash } from '../../router/core/address.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What molecules.cheminfo.org says of a token a site holds:
|
|
5
|
+
*
|
|
6
|
+
* - `ok`: issued for a source the site writes, and not revoked;
|
|
7
|
+
* - `otherSource`: valid, but issued for another source, so what the site
|
|
8
|
+
* sends is credited to that one;
|
|
9
|
+
* - `refused`: never issued, or revoked;
|
|
10
|
+
* - `unset`: the site holds none;
|
|
11
|
+
* - `uncheckable`: molecules.cheminfo.org answers, but cannot check a token
|
|
12
|
+
* yet;
|
|
13
|
+
* - `unanswered`: it did not answer, or failed.
|
|
14
|
+
*/
|
|
15
|
+
export type MoleculesTokenVerdict =
|
|
16
|
+
'ok' | 'otherSource' | 'refused' | 'unset' | 'uncheckable' | 'unanswered';
|
|
17
|
+
|
|
18
|
+
/** A token a site holds, to be checked. */
|
|
19
|
+
export interface MoleculesTokenToCheck {
|
|
20
|
+
/** The environment variable it is read from, e.g. `MOLECULES_TOKEN_LOTUS`. */
|
|
21
|
+
variable: string;
|
|
22
|
+
/** The token, or undefined when the variable is unset or blank. */
|
|
23
|
+
token: string | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* The sources it may have been issued for, as molecules.cheminfo.org names
|
|
26
|
+
* them: any one of them is right, whatever its case.
|
|
27
|
+
* @default [] — any source
|
|
28
|
+
*/
|
|
29
|
+
sources?: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A token, as molecules.cheminfo.org checked it. Never the token itself. */
|
|
33
|
+
export interface MoleculesTokenCheck {
|
|
34
|
+
/** The environment variable it is read from. */
|
|
35
|
+
variable: string;
|
|
36
|
+
/** The sources it may have been issued for; empty for any source. */
|
|
37
|
+
sources: string[];
|
|
38
|
+
/** What molecules.cheminfo.org says of it. */
|
|
39
|
+
verdict: MoleculesTokenVerdict;
|
|
40
|
+
/**
|
|
41
|
+
* What molecules.cheminfo.org said, or why it could not be asked; null when
|
|
42
|
+
* the verdict says it all.
|
|
43
|
+
*/
|
|
44
|
+
message: string | null;
|
|
45
|
+
/** The source molecules.cheminfo.org knows it for, once it answered. */
|
|
46
|
+
source: string | null;
|
|
47
|
+
/**
|
|
48
|
+
* Its first characters, as molecules.cheminfo.org shows them: what tells two
|
|
49
|
+
* tokens apart without showing either.
|
|
50
|
+
*/
|
|
51
|
+
hint: string | null;
|
|
52
|
+
/** Who it was issued to. */
|
|
53
|
+
email: string | null;
|
|
54
|
+
/** When it was issued, in epoch milliseconds. */
|
|
55
|
+
issuedAt: number | null;
|
|
56
|
+
/** When it last read or wrote, in epoch milliseconds, to within a minute. */
|
|
57
|
+
lastUsedAt: number | null;
|
|
58
|
+
/** When it was checked, in epoch milliseconds. */
|
|
59
|
+
checkedAt: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Where molecules.cheminfo.org answers, and how patiently it is asked. */
|
|
63
|
+
export interface CheckMoleculesTokenOptions {
|
|
64
|
+
/** Where it answers, e.g. `https://molecules.cheminfo.org`. */
|
|
65
|
+
baseUrl: string;
|
|
66
|
+
/**
|
|
67
|
+
* How long one check may take.
|
|
68
|
+
* @default 5000
|
|
69
|
+
*/
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* The fetch to use, for tests.
|
|
73
|
+
* @default globalThis.fetch
|
|
74
|
+
*/
|
|
75
|
+
fetch?: typeof fetch;
|
|
76
|
+
/**
|
|
77
|
+
* The clock the checks are stamped with, for tests.
|
|
78
|
+
* @default Date.now
|
|
79
|
+
*/
|
|
80
|
+
now?: () => number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Ask molecules.cheminfo.org about a token: `GET /v1/token`, which says which
|
|
85
|
+
* source it was issued for and never counts as using it. A token pasted in
|
|
86
|
+
* another source's place, revoked, or never issued shows here before an import
|
|
87
|
+
* sends anything with it. Never throws: a molecules.cheminfo.org that does not
|
|
88
|
+
* answer is a verdict too.
|
|
89
|
+
* @param entry - The token, where it is read from, and the sources it may be for.
|
|
90
|
+
* @param options - Where molecules.cheminfo.org answers, and how patiently.
|
|
91
|
+
* @returns What it says of the token.
|
|
92
|
+
*/
|
|
93
|
+
export async function checkMoleculesToken(
|
|
94
|
+
entry: MoleculesTokenToCheck,
|
|
95
|
+
options: CheckMoleculesTokenOptions,
|
|
96
|
+
): Promise<MoleculesTokenCheck> {
|
|
97
|
+
const {
|
|
98
|
+
baseUrl,
|
|
99
|
+
timeoutMs = 5000,
|
|
100
|
+
fetch: fetchImplementation = globalThis.fetch,
|
|
101
|
+
now = Date.now,
|
|
102
|
+
} = options;
|
|
103
|
+
const sources = entry.sources === undefined ? [] : [...entry.sources];
|
|
104
|
+
const verdict = (
|
|
105
|
+
value: MoleculesTokenVerdict,
|
|
106
|
+
message: string | null = null,
|
|
107
|
+
): MoleculesTokenCheck => ({
|
|
108
|
+
variable: entry.variable,
|
|
109
|
+
sources,
|
|
110
|
+
verdict: value,
|
|
111
|
+
message,
|
|
112
|
+
source: null,
|
|
113
|
+
hint: null,
|
|
114
|
+
email: null,
|
|
115
|
+
issuedAt: null,
|
|
116
|
+
lastUsedAt: null,
|
|
117
|
+
checkedAt: now(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const token = entry.token?.trim();
|
|
121
|
+
if (!token) return verdict('unset');
|
|
122
|
+
|
|
123
|
+
let response: Response;
|
|
124
|
+
try {
|
|
125
|
+
response = await fetchImplementation(
|
|
126
|
+
`${trimTrailingSlash(baseUrl)}/v1/token`,
|
|
127
|
+
{
|
|
128
|
+
headers: {
|
|
129
|
+
accept: 'application/json',
|
|
130
|
+
authorization: `Bearer ${token}`,
|
|
131
|
+
},
|
|
132
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
} catch (error: unknown) {
|
|
136
|
+
return verdict('unanswered', whyUnanswered(error, timeoutMs));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (response.status === 401) {
|
|
140
|
+
return verdict('refused', await messageOf(response));
|
|
141
|
+
}
|
|
142
|
+
if (response.status === 404) {
|
|
143
|
+
await response.body?.cancel();
|
|
144
|
+
return verdict('uncheckable', 'GET /v1/token answered 404');
|
|
145
|
+
}
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
await response.body?.cancel();
|
|
148
|
+
return verdict('unanswered', `GET /v1/token answered ${response.status}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const record = await recordOf(response);
|
|
152
|
+
if (record === null) {
|
|
153
|
+
return verdict('unanswered', 'GET /v1/token answered no token');
|
|
154
|
+
}
|
|
155
|
+
const known = {
|
|
156
|
+
source: record.source,
|
|
157
|
+
hint: record.hint,
|
|
158
|
+
email: record.email,
|
|
159
|
+
issuedAt: record.createdAt * 1000,
|
|
160
|
+
lastUsedAt: record.lastUsedAt === null ? null : record.lastUsedAt * 1000,
|
|
161
|
+
};
|
|
162
|
+
if (record.revokedAt !== null) {
|
|
163
|
+
return { ...verdict('refused', 'this token was revoked'), ...known };
|
|
164
|
+
}
|
|
165
|
+
const wanted = record.source.toLowerCase();
|
|
166
|
+
const expected =
|
|
167
|
+
sources.length === 0 ||
|
|
168
|
+
sources.some((source) => source.toLowerCase() === wanted);
|
|
169
|
+
return { ...verdict(expected ? 'ok' : 'otherSource'), ...known };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check every token a site holds, all at once.
|
|
174
|
+
* @param entries - The tokens. See {@link checkMoleculesToken}.
|
|
175
|
+
* @param options - See {@link CheckMoleculesTokenOptions}.
|
|
176
|
+
* @returns What molecules.cheminfo.org says of each, in the order given.
|
|
177
|
+
*/
|
|
178
|
+
export async function checkMoleculesTokens(
|
|
179
|
+
entries: readonly MoleculesTokenToCheck[],
|
|
180
|
+
options: CheckMoleculesTokenOptions,
|
|
181
|
+
): Promise<MoleculesTokenCheck[]> {
|
|
182
|
+
return Promise.all(
|
|
183
|
+
entries.map((entry) => checkMoleculesToken(entry, options)),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* What a page anyone can open may show of a check: the verdict and the source,
|
|
189
|
+
* without who the token was issued to or its first characters.
|
|
190
|
+
* @param check - The check.
|
|
191
|
+
* @returns The check, without them.
|
|
192
|
+
*/
|
|
193
|
+
export function publicTokenCheck(
|
|
194
|
+
check: MoleculesTokenCheck,
|
|
195
|
+
): MoleculesTokenCheck {
|
|
196
|
+
return { ...check, email: null, hint: null };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** What `GET /v1/token` answers, as far as a check reads it. */
|
|
200
|
+
interface TokenRecord {
|
|
201
|
+
source: string;
|
|
202
|
+
email: string;
|
|
203
|
+
hint: string;
|
|
204
|
+
createdAt: number;
|
|
205
|
+
lastUsedAt: number | null;
|
|
206
|
+
revokedAt: number | null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function recordOf(response: Response): Promise<TokenRecord | null> {
|
|
210
|
+
let body: unknown;
|
|
211
|
+
try {
|
|
212
|
+
body = await response.json();
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (typeof body !== 'object' || body === null) return null;
|
|
217
|
+
const record = body as Partial<Record<keyof TokenRecord, unknown>>;
|
|
218
|
+
if (
|
|
219
|
+
typeof record.source !== 'string' ||
|
|
220
|
+
typeof record.email !== 'string' ||
|
|
221
|
+
typeof record.hint !== 'string' ||
|
|
222
|
+
typeof record.createdAt !== 'number' ||
|
|
223
|
+
!isOptionalNumber(record.lastUsedAt) ||
|
|
224
|
+
!isOptionalNumber(record.revokedAt)
|
|
225
|
+
) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
source: record.source,
|
|
230
|
+
email: record.email,
|
|
231
|
+
hint: record.hint,
|
|
232
|
+
createdAt: record.createdAt,
|
|
233
|
+
lastUsedAt: record.lastUsedAt,
|
|
234
|
+
revokedAt: record.revokedAt,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isOptionalNumber(value: unknown): value is number | null {
|
|
239
|
+
return value === null || typeof value === 'number';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function messageOf(response: Response): Promise<string | null> {
|
|
243
|
+
try {
|
|
244
|
+
const body = (await response.json()) as { message?: unknown };
|
|
245
|
+
return typeof body.message === 'string' ? body.message : null;
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function whyUnanswered(error: unknown, timeoutMs: number): string {
|
|
252
|
+
if ((error as { name?: unknown } | null)?.name === 'TimeoutError') {
|
|
253
|
+
return `no answer within ${timeoutMs} ms`;
|
|
254
|
+
}
|
|
255
|
+
return error instanceof Error ? error.message : String(error);
|
|
256
|
+
}
|