feat: release toolbox portal 0.1.0
This commit is contained in:
143
src/catalogue.ts
Normal file
143
src/catalogue.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import {
|
||||
contextualizeToolboxLink,
|
||||
fetchToolboxAppManifest,
|
||||
fetchToolboxCatalog,
|
||||
requireSameOrigin,
|
||||
resolveToolboxApp,
|
||||
resolveWebUrl,
|
||||
type ToolboxCatalogEntry,
|
||||
type ToolboxCatalogExternalEntry,
|
||||
type ToolboxCatalogManifestEntry,
|
||||
} from '@add-ideas/toolbox-contract';
|
||||
import type { LoadedCatalogue, ResolvedApp, UnavailableApp } from './types';
|
||||
|
||||
function isInternal(
|
||||
entry: ToolboxCatalogEntry
|
||||
): entry is ToolboxCatalogManifestEntry {
|
||||
return 'manifest' in entry;
|
||||
}
|
||||
|
||||
function fallbackId(entry: ToolboxCatalogExternalEntry, entryUrl: URL): string {
|
||||
const slug = entry.name
|
||||
.toLocaleLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '');
|
||||
let hash = 2166136261;
|
||||
for (const character of `${entry.name}\0${entryUrl.href}`) {
|
||||
hash ^= character.codePointAt(0) ?? 0;
|
||||
hash = Math.imul(hash, 16777619);
|
||||
}
|
||||
return `external:${slug || 'tool'}:${(hash >>> 0).toString(36)}`;
|
||||
}
|
||||
|
||||
export async function loadCatalogue(
|
||||
catalogueHref: string,
|
||||
signal?: AbortSignal
|
||||
): Promise<LoadedCatalogue> {
|
||||
const pageUrl = resolveWebUrl(document.baseURI);
|
||||
const catalogueUrl = requireSameOrigin(
|
||||
resolveWebUrl(catalogueHref, pageUrl),
|
||||
pageUrl,
|
||||
'Toolbox catalogue'
|
||||
);
|
||||
const catalogue = await fetchToolboxCatalog(catalogueUrl, {
|
||||
signal,
|
||||
expectedOrigin: pageUrl,
|
||||
});
|
||||
const enabled = catalogue.apps.filter((entry) => entry.enabled);
|
||||
const results = await Promise.all(
|
||||
enabled.map(
|
||||
async (
|
||||
entry
|
||||
): Promise<{ app?: ResolvedApp; unavailable?: UnavailableApp }> => {
|
||||
if (!isInternal(entry)) {
|
||||
const entryUrl = resolveWebUrl(entry.entry, catalogueUrl);
|
||||
return {
|
||||
app: {
|
||||
id: fallbackId(entry, entryUrl),
|
||||
name: entry.name,
|
||||
description: 'External tool',
|
||||
entryUrl: entryUrl.href,
|
||||
categories: [],
|
||||
tags: [],
|
||||
launchModes: [entry.launch],
|
||||
isExternal: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const manifestUrl = requireSameOrigin(
|
||||
resolveWebUrl(entry.manifest, catalogueUrl),
|
||||
pageUrl,
|
||||
'Application manifest'
|
||||
);
|
||||
try {
|
||||
const manifest = await fetchToolboxAppManifest(manifestUrl, {
|
||||
signal,
|
||||
expectedOrigin: pageUrl,
|
||||
});
|
||||
const resolved = resolveToolboxApp(manifest, manifestUrl, pageUrl);
|
||||
return {
|
||||
app: {
|
||||
id: manifest.id,
|
||||
name: manifest.name,
|
||||
version: manifest.version,
|
||||
description: manifest.description,
|
||||
entryUrl: resolved.entryUrl.href,
|
||||
iconUrl: resolved.iconUrl.href,
|
||||
categories: manifest.categories,
|
||||
tags: manifest.tags,
|
||||
launchModes: manifest.integration.launchModes,
|
||||
isExternal: false,
|
||||
manifestUrl: resolved.manifestUrl.href,
|
||||
sourceUrl: manifest.source?.repository,
|
||||
requirements: manifest.requirements,
|
||||
privacy: manifest.privacy,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
if (signal?.aborted) throw error;
|
||||
return {
|
||||
unavailable: {
|
||||
id: `unavailable:${manifestUrl.href}`,
|
||||
name: manifestUrl.pathname.split('/').at(-2) ?? 'Unknown app',
|
||||
manifestUrl: manifestUrl.href,
|
||||
reason:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Unknown manifest error.',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const apps = results.flatMap((result) => (result.app ? [result.app] : []));
|
||||
const ids = new Set<string>();
|
||||
for (const app of apps) {
|
||||
if (ids.has(app.id)) throw new Error(`Duplicate app id: ${app.id}.`);
|
||||
ids.add(app.id);
|
||||
}
|
||||
|
||||
return {
|
||||
catalogue,
|
||||
catalogueUrl,
|
||||
homeUrl: requireSameOrigin(
|
||||
resolveWebUrl(catalogue.home, catalogueUrl),
|
||||
pageUrl,
|
||||
'Toolbox home'
|
||||
).href,
|
||||
apps,
|
||||
unavailable: results.flatMap((result) =>
|
||||
result.unavailable ? [result.unavailable] : []
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function contextualLaunchUrl(
|
||||
entryHref: string,
|
||||
catalogueHref: string
|
||||
): string {
|
||||
return contextualizeToolboxLink(entryHref, catalogueHref);
|
||||
}
|
||||
Reference in New Issue
Block a user