Merge pull request #1479 from umbraco/feature/public-extensions

Feature/public-extensions
This commit is contained in:
Niels Lyngsø
2024-03-25 09:49:55 +01:00
committed by GitHub
3 changed files with 68 additions and 48 deletions

View File

@@ -55,7 +55,7 @@ export class UmbBackofficeElement extends UmbLitElement {
new UmbBackofficeContext(this);
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry);
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry);
new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerAllExtensions();
// So far local packages are this simple to registerer, so no need for a manager to do that:
CORE_PACKAGES.forEach(async (packageImport) => {

View File

@@ -1,4 +1,8 @@
import { PackageResource, OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';
import {
PackageResource,
OpenAPI,
type PackageManifestResponseModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';
@@ -14,56 +18,66 @@ export class UmbServerExtensionRegistrator extends UmbControllerBase {
constructor(host: UmbControllerHost, extensionRegistry: UmbBackofficeExtensionRegistry) {
super(host, UmbServerExtensionRegistrator.name);
this.#extensionRegistry = extensionRegistry;
this.#loadServerPackages();
}
async #loadServerPackages() {
/* TODO: we need a new endpoint here, to remove the dependency on the package repository, to get the modules available for the backoffice scope
/ we will need a similar endpoint for the login, installer etc at some point.
We should expose more information about the packages when not authorized so the end point should only return a list of modules from the manifest with
with the correct scope.
This code is copy pasted from the package repository. We probably don't need this is the package repository anymore.
*/
/**
* Registers all extensions from the server.
* This is used to register all extensions that are available to the user (including private extensions).
* @remark Users must have the BACKOFFICE_ACCESS permission to access this method.
*/
public async registerAllExtensions() {
const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifest());
if (packages) {
// Append packages to the store but only if they have a name
//store.appendItems(packages.filter((p) => p.name?.length));
const extensions: ManifestBase[] = [];
packages.forEach((p) => {
p.extensions?.forEach((e) => {
// Crudely validate that the extension at least follows a basic manifest structure
// Idea: Use `Zod` to validate the manifest
if (isManifestBaseType(e)) {
/**
* Crude check to see if extension is of type "js" since it is safe to assume we do not
* need to load any other types of extensions in the backoffice (we need a js file to load)
*/
// TODO: add helper to check for relative paths
// Add base url if the js path is relative
if ('js' in e && typeof e.js === 'string' && !e.js.startsWith('http')) {
e.js = `${this.#apiBaseUrl}${e.js}`;
}
// Add base url if the element path is relative
if ('element' in e && typeof e.element === 'string' && !e.element.startsWith('http')) {
e.element = `${this.#apiBaseUrl}${e.element}`;
}
// Add base url if the element path api relative
if ('api' in e && typeof e.api === 'string' && !e.api.startsWith('http')) {
e.api = `${this.#apiBaseUrl}${e.api}`;
}
extensions.push(e);
}
});
});
this.#extensionRegistry.registerMany(extensions);
await this.#loadServerPackages(packages);
}
}
/**
* Registers all public extensions from the server.
* This is used to register all extensions that are available to the user (excluding private extensions) such as login extensions.
* @remark Any user can access this method without any permissions.
*/
public async registerPublicExtensions() {
const { data: packages } = await tryExecuteAndNotify(this, PackageResource.getPackageManifestPublic());
if (packages) {
await this.#loadServerPackages(packages);
}
}
async #loadServerPackages(packages: PackageManifestResponseModel[]) {
const extensions: ManifestBase[] = [];
packages.forEach((p) => {
p.extensions?.forEach((e) => {
// Crudely validate that the extension at least follows a basic manifest structure
// Idea: Use `Zod` to validate the manifest
if (isManifestBaseType(e)) {
/**
* Crude check to see if extension is of type "js" since it is safe to assume we do not
* need to load any other types of extensions in the backoffice (we need a js file to load)
*/
// TODO: add helper to check for relative paths
// Add base url if the js path is relative
if ('js' in e && typeof e.js === 'string' && !e.js.startsWith('http')) {
e.js = `${this.#apiBaseUrl}${e.js}`;
}
// Add base url if the element path is relative
if ('element' in e && typeof e.element === 'string' && !e.element.startsWith('http')) {
e.element = `${this.#apiBaseUrl}${e.element}`;
}
// Add base url if the element path api relative
if ('api' in e && typeof e.api === 'string' && !e.api.startsWith('http')) {
e.api = `${this.#apiBaseUrl}${e.api}`;
}
extensions.push(e);
}
});
});
this.#extensionRegistry.registerMany(extensions);
}
}

View File

@@ -27,6 +27,12 @@ export interface UmbracoPackage {
*/
allowTelemetry?: boolean;
/**
* @title Decides if the package is allowed to be accessed by the public, e.g. on the login screen
* @default false
*/
allowPublicAccess?: boolean;
/**
* @title An array of Umbraco package manifest types that will be installed
* @required