@kit-data-manager/pid-component
Version:
The PID-Component is a web component that can be used to evaluate and display FAIR Digital Objects, PIDs, ORCiDs, and possibly other identifiers in a user-friendly way. It is easily extensible to support other identifier types.
271 lines (270 loc) • 12.1 kB
JavaScript
/*!
*
* Copyright 2024 Karlsruhe Institute of Technology.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { h } from "@stencil/core";
import { GenericIdentifierType } from "../utils/GenericIdentifierType";
import { FoldableItem } from "../utils/FoldableItem";
import { FoldableAction } from "../utils/FoldableAction";
const urlRegex = new RegExp('^https?://spdx.org/licenses/[\\w.\\-+]+/?$', 'i');
export class SPDXType extends GenericIdentifierType {
constructor() {
super(...arguments);
this.licenseData = null;
this.licenseId = '';
this.corsFallback = true;
this.corsProxy = 'https://corsproxy.io/?';
this.spdxBaseUrl = 'https://spdx.org/licenses';
this.fileFormat = 'json';
this.requestTimeout = 10000;
}
getSettingsKey() {
return 'SPDXType';
}
async hasCorrectFormat() {
const isValidUrl = urlRegex.test(this.value);
const idRegex = /^[\w.\-+]+$/;
const isValidId = idRegex.test(this.value);
if (!isValidUrl && !isValidId) {
return false;
}
let licenseId;
if (isValidUrl) {
licenseId = this.value.replace(/^https?:\/\/spdx\.org\/licenses\//i, '').replace(/\/$/, '');
}
else {
licenseId = this.value;
}
const isValid = await this.validateLicense(licenseId);
console.log('SPDX License validation result:', {
licenseId,
isValid,
licenseData: this.licenseData,
});
return isValid;
}
async validateLicense(licenseId) {
try {
console.log(`Validating SPDX license: ${licenseId}`);
this.licenseId = licenseId;
const url = this.buildLicenseApiUrl(licenseId);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) {
console.log(`License ${licenseId} validation failed with status ${response.status}`);
return false;
}
const data = await response.json();
console.log(`License API response for ${licenseId}:`, data);
if (data && data.licenseId) {
this.licenseData = data;
console.log(`License data populated for ${licenseId}:`, this.licenseData);
return true;
}
console.log(`Invalid license data received for ${licenseId}`);
return false;
}
catch (error) {
console.error(`License validation error for ${licenseId}:`, error);
return false;
}
}
buildLicenseApiUrl(licenseId) {
const baseUrl = this.corsFallback
? `${this.corsProxy}${encodeURIComponent(`${this.spdxBaseUrl}/${licenseId}.${this.fileFormat}`)}`
: `${this.spdxBaseUrl}/${licenseId}.${this.fileFormat}`;
return baseUrl;
}
get data() {
return this.licenseData;
}
logLicenseData() {
console.log('Current license data:', {
licenseId: this.licenseId,
licenseData: this.licenseData,
hasData: Boolean(this.licenseData && this.licenseData.licenseId && this.licenseData.name),
});
}
async init() {
try {
if (!this.licenseId) {
this.extractLicenseIdFromInput();
}
if (!this.licenseData) {
await this.fetchLicenseData();
}
else {
console.debug(`Using pre-fetched data for license ${this.licenseId}`);
}
this.populateLicenseData();
this.addActionButtons();
}
catch (error) {
console.error('Error fetching SPDX data:', error);
this.handleInitError(error);
}
}
extractLicenseIdFromInput() {
if (!this.value.includes('/') && !this.value.includes('://')) {
this.licenseId = this.value.trim();
}
else {
this.licenseId = this.value
.replace(/^https?:\/\/spdx\.org\/licenses\//i, '')
.replace(/\/$/, '')
.replace(/\.(json|html)$/i, '');
}
}
async fetchLicenseData() {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), this.requestTimeout);
});
try {
const apiUrl = this.buildApiUrl();
console.debug(`Fetching license data from: ${apiUrl}`);
const fetchPromise = fetch(apiUrl);
const response = (await Promise.race([fetchPromise, timeout]));
if (!response.ok) {
if (this.corsFallback && apiUrl.includes(this.corsProxy)) {
await this.tryFallbackOptions();
}
else {
throw new Error(`Failed to fetch SPDX data: ${response.status}`);
}
}
else {
this.licenseData = await response.json();
}
if (!this.licenseData) {
throw new Error('No license data available');
}
}
catch (fetchError) {
console.warn('Error fetching SPDX license data:', fetchError);
throw fetchError;
}
}
async tryFallbackOptions() {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), this.requestTimeout);
});
const directUrl = `${this.spdxBaseUrl}/${this.licenseId}.${this.fileFormat}`;
const directFetchPromise = fetch(directUrl);
const directResponse = (await Promise.race([directFetchPromise, timeout]));
if (!directResponse.ok) {
throw new Error(`Failed to fetch SPDX data: ${directResponse.status}`);
}
this.licenseData = await directResponse.json();
}
buildApiUrl() {
return this.buildLicenseApiUrl(this.licenseId);
}
populateLicenseData() {
if (!this.licenseData)
return;
this.items.push(new FoldableItem(0, 'Full Name', this.licenseData.name, 'The full legal name of the license', null, null, false));
this.items.push(new FoldableItem(10, 'SPDX ID', this.licenseData.licenseId, 'The unique SPDX identifier for this license', null, null, false));
this.addDeprecationInfo();
this.items.push(new FoldableItem(20, 'OSI Approved', this.licenseData.isOsiApproved ? 'Yes' : 'No', 'Whether the license is approved by the Open Source Initiative', 'https://opensource.org/licenses/', null, false));
this.addFsfInfo();
this.addRelatedUrls();
}
addDeprecationInfo() {
if (!this.licenseData || !this.licenseData.isDeprecatedLicenseId)
return;
this.items.push(new FoldableItem(15, 'Deprecated', 'Yes', 'This license ID has been deprecated by SPDX', null, null, false));
if (this.licenseData.deprecatedVersion) {
this.items.push(new FoldableItem(16, 'Deprecated Since', this.licenseData.deprecatedVersion, 'The SPDX version when this license was deprecated', null, null, false));
}
}
addFsfInfo() {
if (!this.licenseData || this.licenseData.isFsfLibre === undefined)
return;
this.items.push(new FoldableItem(25, 'FSF Free/Libre', this.licenseData.isFsfLibre ? 'Yes' : 'No', 'Whether the license is considered "Free" by the Free Software Foundation', 'https://www.fsf.org/licensing/', null, false));
}
addRelatedUrls() {
if (!this.licenseData || !this.licenseData.seeAlso || this.licenseData.seeAlso.length === 0)
return;
for (let i = 0; i < this.licenseData.seeAlso.length; i++) {
const url = this.licenseData.seeAlso[i];
this.items.push(new FoldableItem(30 + i, `Related URL`, url, 'A related URL with more information about this license'));
}
}
addActionButtons() {
if (!this.licenseData)
return;
this.actions.push(new FoldableAction(10, 'View on SPDX', `https://spdx.org/licenses/${this.licenseData.licenseId}`, 'primary'));
if (this.licenseData.isOsiApproved) {
this.actions.push(new FoldableAction(20, 'View on OSI', 'https://opensource.org/licenses/', 'secondary'));
}
this.addOfficialLicenseLink();
}
addOfficialLicenseLink() {
if (!this.licenseData || !this.licenseData.seeAlso || this.licenseData.seeAlso.length === 0)
return;
const officialUrl = this.findOfficialUrl(this.licenseData.seeAlso) || this.licenseData.seeAlso[0];
this.actions.push(new FoldableAction(30, 'View Official License', officialUrl, 'secondary'));
}
findOfficialUrl(urls) {
const allowedHosts = ['opensource.org', 'fsf.org', 'gnu.org', 'apache.org', 'creativecommons.org'];
return urls.find((url) => {
try {
const parsedUrl = new URL(url);
return allowedHosts.includes(parsedUrl.host);
}
catch (_a) {
return false;
}
});
}
handleInitError(error) {
if (error.message && error.message.includes('CORS')) {
this.items.push(new FoldableItem(0, 'Error', `CORS error: Cannot access SPDX API due to cross-origin restrictions. The proxy service may be unavailable.`, 'This is a browser security restriction. Try again later or use a different browser.'));
}
else {
this.items.push(new FoldableItem(0, 'Error', `Failed to fetch data from SPDX API: ${error.message}`));
}
this.addBasicErrorInfo();
this.addNetworkIssueInfo();
this.licenseData = {
licenseId: this.licenseId,
name: this.licenseId,
};
}
addBasicErrorInfo() {
if (this.licenseId) {
this.items.push(new FoldableItem(10, 'License ID', this.licenseId, 'The license identifier that was detected'));
this.actions.push(new FoldableAction(10, 'View on SPDX', `https://spdx.org/licenses/${this.licenseId}`, 'primary'));
}
else {
this.licenseId = this.value.replace(/^https?:\/\/spdx\.org\/licenses\//i, '').replace(/\/$/, '');
this.items.push(new FoldableItem(10, 'Possible License ID', this.licenseId, 'Extracted from the input value'));
this.actions.push(new FoldableAction(10, 'Search on SPDX', 'https://spdx.org/licenses/', 'primary'));
}
}
addNetworkIssueInfo() {
this.items.push(new FoldableItem(20, 'Network Issue', 'The SPDX API could not be reached. This may be due to network connectivity issues or the SPDX service being unavailable.', 'Try again when you have internet connectivity'));
}
renderPreview() {
if (!this.licenseData) {
return h("span", { class: `font-mono text-sm` }, "SPDX: ", this.licenseId || this.value);
}
return (h("span", { class: `flex flex-nowrap items-center align-top font-mono` }, h("span", { class: 'items-center px-1' }, h("span", { class: `font-medium` }, this.licenseData.name || this.licenseId), this.licenseData.licenseId && h("span", { class: `ml-1 text-gray-500` }, "(", this.licenseData.licenseId, ")"))));
}
}
//# sourceMappingURL=SPDXType.js.map