-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-16789] introduce extension metadata (#12717)
- Loading branch information
1 parent
f6f4bc9
commit e79dab8
Showing
20 changed files
with
1,773 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/** well-known name for a feature extensible through an extension. */ | ||
export const Site = Object.freeze({ | ||
forwarder: "forwarder", | ||
} as const); | ||
|
||
/** well-known name for a field surfaced from an extension site to a vendor. */ | ||
export const Field = Object.freeze({ | ||
token: "token", | ||
baseUrl: "baseUrl", | ||
domain: "domain", | ||
prefix: "prefix", | ||
} as const); | ||
|
||
/** Permission levels for metadata. */ | ||
export const Permission = Object.freeze({ | ||
/** unless a rule denies access, allow it. If a permission is `null` | ||
* or `undefined` it should be treated as `Permission.default`. | ||
*/ | ||
default: "default", | ||
/** unless a rule allows access, deny it. */ | ||
none: "none", | ||
/** access is explicitly granted to use an extension. */ | ||
allow: "allow", | ||
/** access is explicitly prohibited for this extension. This rule overrides allow rules. */ | ||
deny: "deny", | ||
} as const); |
104 changes: 104 additions & 0 deletions
104
libs/common/src/tools/extension/extension-registry.abstraction.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { ExtensionSite } from "./extension-site"; | ||
import { | ||
ExtensionMetadata, | ||
ExtensionSet, | ||
ExtensionPermission, | ||
SiteId, | ||
SiteMetadata, | ||
VendorId, | ||
VendorMetadata, | ||
} from "./type"; | ||
|
||
/** Tracks extension sites and the vendors that extend them. */ | ||
export abstract class ExtensionRegistry { | ||
/** Registers a site supporting extensibility. | ||
* Each site may only be registered once. Calls after the first for | ||
* the same SiteId have no effect. | ||
* @param site identifies the site being extended | ||
* @param meta configures the extension site | ||
* @return self for method chaining. | ||
* @remarks The registry initializes with a set of allowed sites and fields. | ||
* `registerSite` drops a registration and trims its allowed fields to only | ||
* those indicated in the allow list. | ||
*/ | ||
abstract registerSite: (meta: SiteMetadata) => this; | ||
|
||
/** List all registered extension sites with their extension permission, if any. | ||
* @returns a list of all extension sites. `permission` is defined when the site | ||
* is associated with an extension permission. | ||
*/ | ||
abstract sites: () => { site: SiteMetadata; permission?: ExtensionPermission }[]; | ||
|
||
/** Get a site's metadata | ||
* @param site identifies a site registration | ||
* @return the site's metadata or `undefined` if the site isn't registered. | ||
*/ | ||
abstract site: (site: SiteId) => SiteMetadata | undefined; | ||
|
||
/** Registers a vendor providing an extension. | ||
* Each vendor may only be registered once. Calls after the first for | ||
* the same VendorId have no effect. | ||
* @param site - identifies the site being extended | ||
* @param meta - configures the extension site | ||
* @return self for method chaining. | ||
*/ | ||
abstract registerVendor: (meta: VendorMetadata) => this; | ||
|
||
/** List all registered vendors with their permissions, if any. | ||
* @returns a list of all extension sites. `permission` is defined when the site | ||
* is associated with an extension permission. | ||
*/ | ||
abstract vendors: () => { vendor: VendorMetadata; permission?: ExtensionPermission }[]; | ||
|
||
/** Get a vendor's metadata | ||
* @param site identifies a vendor registration | ||
* @return the vendor's metadata or `undefined` if the vendor isn't registered. | ||
*/ | ||
abstract vendor: (vendor: VendorId) => VendorMetadata | undefined; | ||
|
||
/** Registers an extension provided by a vendor to an extension site. | ||
* The vendor and site MUST be registered before the extension. | ||
* Each extension may only be registered once. Calls after the first for | ||
* the same SiteId and VendorId have no effect. | ||
* @param site - identifies the site being extended | ||
* @param meta - configures the extension site | ||
* @return self for method chaining. | ||
*/ | ||
abstract registerExtension: (meta: ExtensionMetadata) => this; | ||
|
||
/** Get an extensions metadata | ||
* @param site identifies the extension's site | ||
* @param vendor identifies the extension's vendor | ||
* @return the extension's metadata or `undefined` if the extension isn't registered. | ||
*/ | ||
abstract extension: (site: SiteId, vendor: VendorId) => ExtensionMetadata | undefined; | ||
|
||
/** List all registered extensions and their permissions */ | ||
abstract extensions: () => ReadonlyArray<{ | ||
extension: ExtensionMetadata; | ||
permissions: ExtensionPermission[]; | ||
}>; | ||
|
||
/** Registers a permission. Only 1 permission can be registered for each extension set. | ||
* Calls after the first *replace* the registered permission. | ||
* @param set the collection of extensions affected by the permission | ||
* @param permission the permission for the collection | ||
* @return self for method chaining. | ||
*/ | ||
abstract setPermission: (set: ExtensionSet, permission: ExtensionPermission) => this; | ||
|
||
/** Retrieves the current permission for the given extension set or `undefined` if | ||
* a permission doesn't exist. | ||
*/ | ||
abstract permission: (set: ExtensionSet) => ExtensionPermission | undefined; | ||
|
||
/** Returns all registered extension rules. */ | ||
abstract permissions: () => { set: ExtensionSet; permission: ExtensionPermission }[]; | ||
|
||
/** Creates a point-in-time snapshot of the registry's contents with extension | ||
* permissions applied for the provided SiteId. | ||
* @param id identifies the extension site to create. | ||
* @returns the extension site, or `undefined` if the site is not registered. | ||
*/ | ||
abstract build: (id: SiteId) => ExtensionSite | undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { deepFreeze } from "../util"; | ||
|
||
import { ExtensionMetadata, SiteMetadata, VendorId } from "./type"; | ||
|
||
/** Describes the capabilities of an extension site. | ||
* This type is immutable. | ||
*/ | ||
export class ExtensionSite { | ||
/** instantiate the extension site | ||
* @param site describes the extension site | ||
* @param vendors describes the available vendors | ||
* @param extensions describes the available extensions | ||
*/ | ||
constructor( | ||
readonly site: Readonly<SiteMetadata>, | ||
readonly extensions: ReadonlyMap<VendorId, Readonly<ExtensionMetadata>>, | ||
) { | ||
deepFreeze(this); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { DefaultFields, DefaultSites, Extension } from "./metadata"; | ||
import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; | ||
import { VendorExtensions, Vendors } from "./vendor"; | ||
|
||
// FIXME: find a better way to build the registry than a hard-coded factory function | ||
|
||
/** Constructs the extension registry */ | ||
export function buildExtensionRegistry() { | ||
const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); | ||
|
||
for (const site of Reflect.ownKeys(Extension) as string[]) { | ||
registry.registerSite(Extension[site]); | ||
} | ||
|
||
for (const vendor of Vendors) { | ||
registry.registerVendor(vendor); | ||
} | ||
|
||
for (const extension of VendorExtensions) { | ||
registry.registerExtension(extension); | ||
} | ||
|
||
return registry; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export { Site, Field, Permission } from "./data"; | ||
export { | ||
SiteId, | ||
FieldId, | ||
VendorId, | ||
ExtensionId, | ||
ExtensionPermission, | ||
SiteMetadata, | ||
ExtensionMetadata, | ||
VendorMetadata, | ||
} from "./type"; | ||
export { ExtensionSite } from "./extension-site"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Field, Site, Permission } from "./data"; | ||
import { FieldId, SiteId, SiteMetadata } from "./type"; | ||
|
||
export const DefaultSites: SiteId[] = Object.freeze(Object.keys(Site) as any); | ||
|
||
export const DefaultFields: FieldId[] = Object.freeze(Object.keys(Field) as any); | ||
|
||
export const Extension: Record<string, SiteMetadata> = { | ||
[Site.forwarder]: { | ||
id: Site.forwarder, | ||
availableFields: [Field.baseUrl, Field.domain, Field.prefix, Field.token], | ||
}, | ||
}; | ||
|
||
export const AllowedPermissions: ReadonlyArray<keyof typeof Permission> = Object.freeze( | ||
Object.values(Permission), | ||
); |
Oops, something went wrong.