Skip to content

Commit e3340bf

Browse files
Merge pull request #40 from opencomponents/context-doc
add documentation for context plugins
2 parents 311a89d + 6185b00 commit e3340bf

File tree

1 file changed

+187
-18
lines changed

1 file changed

+187
-18
lines changed

website/docs/registry/registry-configuration.md

Lines changed: 187 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,19 @@ options.routes = [
397397

398398
## Plugins
399399

400-
Plugins are a way to extend registry's context allowing components to inherit custom functionalities.
400+
Plugins are a way to extend registry's context allowing components to inherit custom functionalities. They can be configured to receive component context information (name and version) when needed for enhanced security and decision-making capabilities.
401401

402-
This is a plugin example:
402+
### Plugin Structure
403+
404+
A plugin consists of two main parts:
405+
406+
- **register**: Function called during plugin initialization
407+
- **execute**: Function that provides the actual plugin functionality
408+
- **context** (optional): Boolean flag to enable component context awareness
409+
410+
### Basic Plugin Example
411+
412+
This is a basic plugin example without context awareness:
403413

404414
```js
405415
// ./registry/oc-plugins/hobknob.js
@@ -418,53 +428,212 @@ module.exports.execute = function (featureName) {
418428
};
419429
```
420430

421-
This is how to register it in a registry:
431+
### Context-Aware Plugin Example
432+
433+
When you need to make decisions based on which component is calling the plugin, you can enable context awareness:
434+
435+
```js
436+
// ./registry/oc-plugins/feature-flags.js
437+
var connection,
438+
client = require("./feature-flags-client");
439+
440+
module.exports.register = function (options, dependencies, next) {
441+
client.connect(options.connectionString, function (err, conn) {
442+
connection = conn;
443+
next();
444+
});
445+
};
446+
447+
// With context: true, execute receives component context and returns a function
448+
module.exports.execute = function (context) {
449+
// context contains: { name: "component-name", version: "1.2.3" }
450+
return function (featureName) {
451+
// Validate if this component is allowed to access this feature
452+
if (
453+
context.name.startsWith("internal/") &&
454+
featureName.startsWith("public/")
455+
) {
456+
throw new Error("Internal components cannot access public features");
457+
}
458+
459+
// Log feature access for audit purposes
460+
console.log(
461+
`Component ${context.name}@${context.version} accessing feature: ${featureName}`
462+
);
463+
464+
return connection.get(featureName);
465+
};
466+
};
467+
468+
// Enable context awareness
469+
module.exports.context = true;
470+
```
471+
472+
### Plugin Registration
473+
474+
This is how to register plugins in a registry:
422475

423476
```js
424477
// ./registry/init.js
425-
...
478+
var oc = require("oc");
426479
var registry = new oc.Registry(configuration);
427480

481+
// Register a basic plugin
428482
registry.register({
429-
name: 'getFeatureSwitch',
430-
register: require('./oc-plugins/hobknob'),
483+
name: "getFeatureSwitch",
484+
register: require("./oc-plugins/hobknob"),
431485
options: {
432-
connectionString: connectionString
433-
}
486+
connectionString: connectionString,
487+
},
488+
});
489+
490+
// Register a context-aware plugin
491+
registry.register({
492+
name: "getSecureFeature",
493+
register: require("./oc-plugins/feature-flags"),
494+
options: {
495+
connectionString: connectionString,
496+
},
434497
});
435-
...
436498
```
437499

438-
This is how to use a plugin from a component:
500+
### Using Plugins from Components
501+
502+
Components use plugins the same way regardless of whether they have context awareness:
439503

440504
```js
441505
// ./my-component/server.js
442506
module.exports.data = function (context, callback) {
443507
callback(null, {
444-
variable: context.plugins.getFeatureSwitch("AbTestHomePage"),
508+
// Basic plugin usage
509+
basicFeature: context.plugins.getFeatureSwitch("AbTestHomePage"),
510+
511+
// Context-aware plugin usage (same interface)
512+
secureFeature: context.plugins.getSecureFeature("AdminPanel"),
445513
});
446514
};
447515
```
448516

449-
This is how to depend on (and use) other plugins:
517+
### When to Use Context Awareness
518+
519+
Context awareness is useful when you need to:
520+
521+
1. **Security Validation**: Restrict certain components from accessing specific features
522+
2. **Audit Logging**: Track which components are using which features
523+
3. **Component-Specific Logic**: Provide different behavior based on component name/version
524+
4. **Rate Limiting**: Apply different limits per component
525+
5. **Feature Rollouts**: Enable features only for specific components or versions
526+
527+
### Advanced Context-Aware Plugin Example
528+
529+
Here's a more sophisticated example that demonstrates multiple use cases:
450530

451531
```js
452-
// ./registry/oc-plugins/hobknob.js
532+
// ./registry/oc-plugins/advanced-feature-manager.js
453533
var connection,
454-
client = require("./hobknob-client");
534+
client = require("./feature-manager-client");
535+
536+
module.exports.register = function (options, dependencies, next) {
537+
client.connect(options.connectionString, function (err, conn) {
538+
connection = conn;
539+
next();
540+
});
541+
};
542+
543+
module.exports.execute = function (context) {
544+
return function (featureName, options = {}) {
545+
// Security: Check component permissions
546+
const componentPermissions = getComponentPermissions(context.name);
547+
if (!componentPermissions.canAccess(featureName)) {
548+
throw new Error(
549+
`Component ${context.name} is not authorized to access ${featureName}`
550+
);
551+
}
552+
553+
// Audit: Log all feature access
554+
logFeatureAccess({
555+
component: context.name,
556+
version: context.version,
557+
feature: featureName,
558+
timestamp: new Date().toISOString(),
559+
});
560+
561+
// Rate limiting: Apply different limits per component
562+
const rateLimit = getRateLimit(context.name, featureName);
563+
if (isRateLimited(context.name, featureName, rateLimit)) {
564+
throw new Error(
565+
`Rate limit exceeded for ${context.name} accessing ${featureName}`
566+
);
567+
}
568+
569+
// Version-specific logic
570+
if (context.version.startsWith("2.")) {
571+
// New API for v2+ components
572+
return connection.getV2(featureName, options);
573+
} else {
574+
// Legacy API for older components
575+
return connection.getV1(featureName);
576+
}
577+
};
578+
};
579+
580+
module.exports.context = true;
581+
```
582+
583+
### Plugin Dependencies
584+
585+
Plugins can depend on other plugins, and context awareness works with dependencies:
586+
587+
```js
588+
// ./registry/oc-plugins/secure-logger.js
589+
var connection,
590+
client = require("./logger-client");
455591

456592
module.exports.dependencies = ["log", "otherplugin"];
457593

458594
module.exports.register = function (options, dependencies, next) {
459-
// this register function is only called after all dependencies are registered
595+
// This register function is only called after all dependencies are registered
460596
client.connect(options.connectionString, function (err, conn) {
461597
connection = conn;
462-
dependencies.log("hobknob client initialised");
598+
dependencies.log("secure logger client initialized");
463599
next();
464600
});
465601
};
466602

467-
module.exports.execute = function (featureName) {
468-
return connection.get(featureName);
603+
module.exports.execute = function (context) {
604+
return function (message, level = "info") {
605+
// Add component context to all log messages
606+
const enrichedMessage = {
607+
component: context.name,
608+
version: context.version,
609+
message: message,
610+
level: level,
611+
timestamp: new Date().toISOString(),
612+
};
613+
614+
return connection.log(enrichedMessage);
615+
};
469616
};
617+
618+
module.exports.context = true;
619+
```
620+
621+
### Plugin Configuration Options
622+
623+
| Property | Type | Required | Default | Description |
624+
| ---------- | ------- | -------- | ------- | ------------------------------------------------- |
625+
| `name` | string | yes | - | Unique identifier for the plugin |
626+
| `register` | object | yes | - | Plugin module with register and execute functions |
627+
| `options` | object | no | `{}` | Configuration options passed to the plugin |
628+
| `context` | boolean | no | `false` | Enable component context awareness |
629+
630+
### Context Object Structure
631+
632+
When `context: true` is set, the context object passed to the execute function contains:
633+
634+
```js
635+
{
636+
name: "component-name", // The name of the component calling the plugin
637+
version: "1.2.3" // The version of the component calling the plugin
638+
}
470639
```

0 commit comments

Comments
 (0)