@finos/legend-application
Version:
Legend application core
218 lines • 9.7 kB
JavaScript
/**
* 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