UNPKG

@finos/legend-application

Version:
218 lines 9.7 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * 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 * * http://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 { assertErrorThrown, LogEvent, guaranteeNonEmptyString, assertNonNullable, NetworkClient, createRegExpPatternFromWildcardPattern, } from '@finos/legend-shared'; import { APPLICATION_EVENT } from '../__lib__/LegendApplicationEvent.js'; import { collectKeyedDocumentationEntriesFromConfig, } from '../stores/DocumentationService.js'; import { ApplicationStore, } from '../stores/ApplicationStore.js'; import { registerDownloadHelperServiceWorker } from '../util/DownloadHelperServiceWorker.js'; export class LegendApplicationLogger { } export class LegendApplicationWebConsole extends LegendApplicationLogger { debug(event, ...data) { // eslint-disable-next-line no-console console.debug(`[${event.timestamp}] ${event.name} ${data.length ? ':' : ''}`, ...data); } info(event, ...data) { // eslint-disable-next-line no-console console.info(`[${event.timestamp}] ${event.name} ${data.length ? ':' : ''}`, ...data); } warn(event, ...data) { // eslint-disable-next-line no-console console.warn(`[${event.timestamp}] ${event.name} ${data.length ? ':' : ''}`, ...data); } error(event, ...data) { // eslint-disable-next-line no-console console.error(`[${event.timestamp}] ${event.name} ${data.length ? ':' : ''}`, ...data); } } export class LegendApplication { config; logger; pluginManager; basePresets = []; basePlugins = []; baseAddress; pluginRegister; _isConfigured = false; downloadHelperServiceWorkerPath; downloadHelper = false; releaseNotes; constructor(pluginManager) { this.pluginManager = pluginManager; this.logger = new LegendApplicationWebConsole(); } setup(options) { this.baseAddress = guaranteeNonEmptyString(options.baseAddress, `Can't setup application: 'baseAddress' is missing or empty`); this.pluginRegister = options.pluginRegister; this._isConfigured = true; return this; } withBasePresets(presets) { this.basePresets = presets; return this.withPresets([]); // this will reset the preset list and prepend with base presets } withBasePlugins(plugins) { this.basePlugins = plugins; return this.withPlugins([]); // this will reset the plugin list and prepend with base plugins } withPresets(presets) { this.pluginManager.usePresets([...this.basePresets, ...presets]); return this; } withPlugins(plugins) { this.pluginManager.usePlugins([...this.basePlugins, ...plugins]); return this; } withDownloadHelper(path) { this.downloadHelper = true; this.downloadHelperServiceWorkerPath = path; return this; } withReleaseNotes(releaseNotes) { this.releaseNotes = releaseNotes; return this; } setupApplicationStore(store) { if (this.releaseNotes) { store.releaseNotesService.configure(this.releaseNotes); } } async fetchApplicationConfiguration() { const client = new NetworkClient(); // app config let configData; try { configData = await client.get(`${window.location.origin}${this.baseAddress}config.json`); } catch (error) { assertErrorThrown(error); this.logger.error(LogEvent.create(APPLICATION_EVENT.APPLICATION_CONFIGURATION__FAILURE), error); } assertNonNullable(configData, `Can't fetch Legend application configuration`); // app version let versionData; try { versionData = await client.get(`${window.location.origin}${this.baseAddress}version.json`); } catch (error) { assertErrorThrown(error); this.logger.error(LogEvent.create(APPLICATION_EVENT.APPLICATION_CONFIGURATION__FAILURE), error); } assertNonNullable(versionData, `Can't fetch Legend application version`); return [ await this.configureApplication({ configData, versionData, baseAddress: this.baseAddress, }), configData.extensions ?? {}, ]; } async loadDocumentationRegistryData(config) { const entries = {}; await Promise.all([ ...config.documentationRegistryEntries, ...this.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraDocumentationRegistryEntries?.() ?? []), ].map(async (entry) => { try { const client = new NetworkClient(entry.simple ? { /** * NOTE: see the documentation for `simple` flag {@link DocumentationRegistryEntry} * here, basically, we expect to fetch just the JSON from an endpoint where `Access-Control-Allow-Origin", "*"` is set * as such, we must not include the credential in our request * See https://stackoverflow.com/questions/19743396/cors-cannot-use-wildcard-in-access-control-allow-origin-when-credentials-flag-i */ options: { credentials: 'omit', }, } : {}); const docData = await client.get(guaranteeNonEmptyString(entry.url, `Can't load documentation registry: 'url' field is missing or empty`)); assertNonNullable(docData.entries, `Can't load documentation registry data: 'entries' field is missing`); const patterns = entry.includes?.map((filter) => createRegExpPatternFromWildcardPattern(filter)); Object.entries(docData.entries).forEach(([key, docEntry]) => { if (!patterns || patterns.some((pattern) => pattern.test(key))) { // NOTE: entries will NOT override if (!entries[key]) { entries[key] = docEntry; } } }); } catch (error) { assertErrorThrown(error); this.logger.warn(LogEvent.create(APPLICATION_EVENT.DOCUMENTATION_FETCH__FAILURE), error); } })); // NOTE: config entries will override config.keyedDocumentationEntries = [ ...collectKeyedDocumentationEntriesFromConfig(entries), ...config.keyedDocumentationEntries, ]; } async start() { assertNonNullable(this._isConfigured, 'Legend application has not been configured properly. Make sure to run setup() before start()'); try { // fetch application config const [config, extensionConfigData] = await this.fetchApplicationConfiguration(); this.config = config; // setup plugins this.pluginRegister?.(this.pluginManager, this.config); this.pluginManager.configure(extensionConfigData); this.pluginManager.install(); // other setups await Promise.all( // NOTE: to be done in parallel to save time [this.loadDocumentationRegistryData(config)]); // setup application store const applicationStore = new ApplicationStore(this.config, this.pluginManager); await Promise.all(this.pluginManager .getApplicationPlugins() .flatMap((plugin) => plugin.getExtraApplicationSetups?.() ?? []) .map((setup) => setup(applicationStore))); // set up application this.setupApplicationStore(applicationStore); // load application await this.loadApplication(applicationStore); this.logger.info(LogEvent.create(APPLICATION_EVENT.APPLICATION_LOAD__SUCCESS), 'Legend application loaded'); if (this.downloadHelper) { registerDownloadHelperServiceWorker(this.downloadHelperServiceWorkerPath); } } catch (error) { assertErrorThrown(error); this.logger.error(LogEvent.create(APPLICATION_EVENT.APPLICATION_LOAD__FAILURE), 'Failed to load Legend application'); throw error; } } } export const LEGEND_APPLICATION_ROOT_ELEMENT_TAG = 'legend-application-root'; // NOTE: we use a special tag to mount the application to avoid styling conflicts export const getApplicationRootElement = () => { let rootEl = document.getElementsByTagName(LEGEND_APPLICATION_ROOT_ELEMENT_TAG).length ? document.getElementsByTagName(LEGEND_APPLICATION_ROOT_ELEMENT_TAG)[0] : undefined; if (!rootEl) { rootEl = document.createElement(LEGEND_APPLICATION_ROOT_ELEMENT_TAG); document.body.appendChild(rootEl); } rootEl.setAttribute('style', 'height: 100%; width: 100%; display: block'); return rootEl; }; //# sourceMappingURL=LegendApplication.js.map