Extensions
Overview
Extension is a widget without a visual part. Extends the platform API in ways not originally intended. Always implemented as a class.
Extensions are available for all widgets. The functionality brought in this way can be utilized anywhere in the system. Extensions are always public and anyone can use them.
Each project has different Extensions because different vendors need different functionality.
Extension Scopes
Extensions are categorized by scope, which determines their purpose and available methods.
http_client_ext
Custom HTTP client extension for making API requests with project-specific configuration.
Purpose: Provides specialized network request handling with custom headers, authentication, and configuration per vendor/project.
preset_list_ext
Manages widget presets and templates for dashboard composition.
Purpose: Provides pre-configured widget templates that users can add to dashboards.
Methods:
interface PresetListExtension {
widgetTemplatesAccessRole?: string;
presetsByWidgetName: (name: string) => ComposerWidgetT[];
presetsList: (props: { widgets: LayoutGridDataProp[] }) => ComposerWidgetT[];
presetListAsync: (props: PresetListAsyncT) => ComposerWidgetT[];
}
filter_widgets_ext
Filters and modifies widget lists based on dashboard context.
Purpose: Applies dashboard-specific filtering rules to widget lists.
Methods:
interface FilterWidgetsExtension {
updateWidgetListAsync: (
props: IUpdateWidgetListAsync
) => LayoutGridDataProp[];
}
// Usage
const filteredWidgets = await filterWidgetsExt.updateWidgetListAsync({
httpClient,
apiUrl,
useSelectSharedValue,
dashboardId,
widgets,
});
companion_bff_ws_ext
WebSocket companion for real-time bidirectional communication with backend services.
Purpose: Enables real-time data streaming and push notifications from backend.
Types
Extension Metadata
type ExtensionScopeT =
| "companion_bff_ws_ext"
| "preset_list_ext"
| "filter_widgets_ext"
| "http_client_ext";
interface IExtensionMeta {
id: number; // Unique extension ID (ext_id)
name: string; // Extension name (e.g., "@invent/http-client-companion-ext")
version: string; // Version string (e.g., "1.0.0")
extensionVersionId: number; // Version record ID in database
author: string; // Extension author
scope: ExtensionScopeT; // Extension scope
module: string; // Module path for loading (e.g., "./Companion")
config: ConfigExtension; // Configuration object
}
type ConfigExtension = {
pathList: {
method: string; // HTTP method (GET, POST, etc.)
path: string; // API path
}[];
};
API Reference
GET /extensions/list
Returns extensions assigned to the current portal. Called by the portal on load.
Authentication: Border middleware (JWT from portal context)
Response:
[
{
"name": "@invent/http-client-companion-ext",
"id": 313,
"version": "1.0.0",
"module": "./Companion",
"scope": "http_client_ext",
"config": { "pathList": [] },
"author": "author@example.com",
"extensionVersionId": 5
}
]
Loading Mechanism
Extensions use Webpack Module Federation for dynamic loading at runtime.
Loading Flow
- Authentication - User logs in, token validated
- Fetch metadata - Portal calls
GET /extensions/list - Load scripts - For each extension, load
remoteEntry.js:- Development:
http://localhost:5{ext.id}/remoteEntry.js - Production:
${STORE_BASE_URL}/extensions/${ext.id}/${ext.version}/remoteEntry.js
- Development:
- Instantiate - Load companion class and create instance with metadata
Using Extensions
useExtensionsContext Hook
Access loaded extensions in components:
import { useExtensionsContext } from "@invent/platform-api";
const { getExtension, getExtensionsList } = useExtensionsContext();
// Get specific extension by scope
const httpClientExt = getExtension("http_client_ext");
const presetExt = getExtension("preset_list_ext");
Creating Extensions
Companion Class Structure
Extension companions must:
- Export a default class
- Accept
IExtensionMetain constructor - Implement scope-specific methods
import { IExtensionMeta } from "@invent/shared-types";
export default class HttpClientCompanion {
private meta: IExtensionMeta;
private httpClient: any;
constructor(meta: IExtensionMeta) {
this.meta = meta;
}
public init(httpClient: any): void {
this.httpClient = httpClient;
}
public async request(
path: string,
options: RequestOptions
): Promise<Response> {
// Implementation with project-specific configuration
}
}
Module Federation Config
Extension webpack configuration:
// webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "http_client_ext", // Must match scope
filename: "remoteEntry.js",
exposes: {
"./Companion": "./src/companion.ts",
},
shared: {
// Shared dependencies
},
}),
],
};
extension-config.json
Each extension must have an extension-config.json file in the root directory:
{
"ext_id": 587,
"port": 5587,
"name": "@invent/http-client-companion-ext",
"issuer": "Invent",
"scope": "http_client_ext",
"url": "https://gitlab.apptrium.io/invent/frontend/co-project/http-client-co-ext",
"version": "1.1.0",
"default_branch": "master",
"module": "./HttpClientExt",
"config": { "pathList": [] }
}
Fields:
| Field | Description |
|---|---|
| ext_id | Unique extension identifier (gitlab repository id) |
| port | Local dev server port (convention: 5000 + ext_id) |
| name | NPM-style package name |
| issuer | Author/organization name |
| scope | Extension scope type |
| url | Git repository URL |
| version | Current version (semver) |
| default_branch | Git branch for releases |
| module | Exposed module name in webpack federation |
| config | Extension-specific configuration |
Extension Template
Use the template repository to create a new extension:
Template: https://gitlab.apptrium.io/invent/frontend/templates/http-client-extension-template
The template includes:
- Pre-configured webpack module federation setup
- Extension companion class structure
- CI/CD pipeline configuration
- Development server setup