@memberjunction/react-runtime
Version:
Platform-agnostic React component runtime for MemberJunction. Provides core compilation, registry, and execution capabilities for React components in any JavaScript environment.
149 lines (130 loc) • 5.38 kB
text/typescript
/**
* @fileoverview Library registry for approved third-party libraries
* Maintains a secure registry of libraries that components can use
* @module @memberjunction/react-runtime/utilities
*/
import { UserInfo } from "@memberjunction/core";
import { ComponentLibraryEntity, ComponentMetadataEngine } from "@memberjunction/core-entities";
/**
* Library definition in the registry
*/
export interface LibraryDefinition {
/** Library name (e.g., "Chart.js", "lodash") */
name: string;
/** Global variable name when loaded (e.g., "Chart", "_") */
globalVariable: string;
/** Category path for organization (e.g., "charting/advanced", "utility/date") */
category: string;
/** Available versions with their CDN URLs */
versions: {
[]: {
cdnUrl: string;
/** Optional CSS URLs for UI libraries */
cssUrls?: string[];
};
};
/** Default version to use if not specified */
defaultVersion: string;
}
/**
* Registry of approved libraries that components can use
* This is the security boundary - only libraries defined here can be loaded
*/
export class LibraryRegistry {
private static libraries: Map<string, LibraryDefinition> = new Map();
private static _configured: boolean = false;
public static async Config(forceRefresh: boolean = false, componentLibraries: ComponentLibraryEntity[]) {
if (!this._configured || forceRefresh) {
// next up, we need to map the component metadata to the library definitions
// with two steps - first step is that we need to group the libraries in the engine
// by name and then we'll have all the versions for that particular library we can break
// into versions in our structure
const libraryGroups = new Map<string, ComponentLibraryEntity[]>();
for (const lib of componentLibraries) {
if (!libraryGroups.has(lib.Name.toLowerCase())) {
libraryGroups.set(lib.Name.toLowerCase(), []);
}
libraryGroups.get(lib.Name.toLowerCase())!.push(lib);
}
// now we have grouped libraries using the ComponentLibraryEntity type, and next up
// we can map this to our internal structure of LibraryDefinition
for (const [name, versions] of libraryGroups) {
const libDef: LibraryDefinition = {
name,
globalVariable: versions[0].GlobalVariable || "",
category: versions[0].Category || "",
versions: {},
defaultVersion: versions[0].Version || ""
};
for (const version of versions) {
libDef.versions[version.Version!] = {
cdnUrl: version.CDNUrl || "",
cssUrls: version.CDNCssUrl?.split(",") || []
};
}
this.libraries.set(name, libDef);
}
// at this point, our library registry is fully populated
this._configured = true;
}
}
/**
* Get library definition by name
*/
static getLibrary(name: string): LibraryDefinition | undefined {
if (!this._configured)
throw new Error("LibraryRegistry is not configured, call LibraryRegistry.Config() before using!");
return this.libraries.get(name?.trim().toLowerCase());
}
/**
* Get CDN URL for a library
* @param name Library name
* @param version Optional version (uses default if not specified)
* @returns CDN URL or undefined if library/version not found
*/
static getCdnUrl(name: string, version?: string): string | undefined {
if (!this._configured)
throw new Error("LibraryRegistry is not configured, call LibraryRegistry.Config() before using!");
const library = this.libraries.get(name?.trim().toLowerCase());
if (!library) return undefined;
const targetVersion = version || library.defaultVersion;
return library.versions[targetVersion]?.cdnUrl;
}
/**
* Check if a library is approved
*/
static isApproved(name: string): boolean {
if (!this._configured)
throw new Error("LibraryRegistry is not configured, call LibraryRegistry.Config() before using!");
return this.libraries.has(name?.trim().toLowerCase());
}
/**
* Resolve library version based on semver-like pattern
* For now, just returns exact match or default
* TODO: Implement proper semver resolution
*/
static resolveVersion(name: string, versionPattern?: string): string | undefined {
if (!this._configured)
throw new Error("LibraryRegistry is not configured, call LibraryRegistry.Config() before using!");
const library = this.libraries.get(name?.trim().toLowerCase());
if (!library) return undefined;
if (!versionPattern) return library.defaultVersion;
// For now, just check exact match
// TODO: Implement proper semver resolution for patterns like "^4.0.0"
if (library.versions[versionPattern]) {
return versionPattern;
}
// If no exact match, return default
return library.defaultVersion;
}
/**
* Add a library to the registry (for future extensibility)
* This would typically be called during app initialization
* with libraries loaded from a database
*/
static registerLibrary(definition: LibraryDefinition): void {
if (!this._configured)
throw new Error("LibraryRegistry is not configured, call LibraryRegistry.Config() before using!");
this.libraries.set(definition.name?.trim().toLowerCase(), definition);
}
}