The sty
coloration library is currently hard coded inside the Broadcast
class. Because Channel
is a core class which uses Broadcast
that makes core
opinionated about how color should work. This makes it impossible to customize if you:
- Don't want color
- Don't like the
sty
syntax
- Aren't using ANSI color
My high level concept is to create a system of customizable StreamDecorators
that can be attached to TransportStreams
in such a a way that a developer wouldn't have to modify core code AND wouldn't have to modify the transport bundle's (like telnet-networking
code).
The difficulty is that TransportStream
types aren't registered anywhere in the way that EntityLoaders
are registered. This means there is currently no good central place to create the Stream+Decorators
bindings.
Possible solutions
Bundle-centric
In this approach the transport bundle (e.g., telnet-networking
) would entirely be in charge of this: they'd handle the initialization, customization, and management of decorators.
Pros
Cons
- Little to no inter-bundle portability as every bundle might have a different configuration scheme, different decorator interfaces, etc.
- All the work is foisted on the bundle dev
- Likely to unintentionally imply that decorators should be hard coded into a transport bundle
Core-centric
In this approach there would be a base TransportDecorator
class in core in the same way there is a base TransportStream
class. Additionally the core would have to decide upon and own the configuration/binding strategy for Stream+Decorators
Pros
- Easier on bundle developers
- Easier for bundle users
- Portable decorators
- Unified configuration style
Cons
- Less control for stream bundle developers since decorator configuration/binding will happen outside of their code
- Having core do the configuration/binding means that the scheme will have to be more abstract and thereby potentially more complex than if it were up to the bundle itself
For me the core-centric approach seems like the obvious choice. The pieces of that puzzle look something like this:
TransportStream
TransportStream
will need some way to identify themselves in such a way that configuration could link a transport stream to its decorators, my initial idea is that the class should get a new identifier getter, for example:
class TelnetStream extends TransportStream
{
static get identifier() {
return 'telnet';
}
// ...
}
TransportDecoratorRegistry
My idea for how to actually bind decorators to a stream involves creating a new TransportDecoratorRegistry
. This class will be in charge of holding the configuration from ranvier.json
which will look similar to DataLoader
configuration:
// ranvier.json
{
// register available decorators
"transportDecorators": {
"ansi": {
// value follows same rules as DataLoader require config
"require": "./lib/MyAnsiDecorator",
// Serves as the default configuration for this decorator
// which can be overridden by 'config' in the binding below
"config": {
"someDecoratorOption": 25
}
}
},
"transportDecoration": {
"telnet": [
{
"decorator": "ansi",
"config": {
// additional configuration specific to this telnet:ansi binding
"anotherOption": "foobar",
// override of default config from registration
"someDecoratorOption": 12,
}
}
]
}
}
This is really verbose with all the configs but in practice it will look more like this:
{
"streamDecorators": {
"ansi": {
"require": "cool-ansi-decorator"
}
},
"streamDecoration": {
"telnet": [
{ "decorator": "ansi" }
]
}
}
To actually facilitate the usage of these decorators TransportDecoratorRegistry
will have a decorate(TransportStream streamConstructor)
method. This method will be used by transport bundle developers which means if a bundle developer so chooses they can not allow applying decorators:
class TransportDecoratorRegistry {
decorate(streamConstructor) {
const decorators = this.getDecorators(streamConstructor.identifier);
return class extends streamConstructor {
write(message, encoding) {
message = decorators.reduce((acc, d) => d.decorate(acc), message);
super.write(message, encoding);
}
};
},
}
Writing a decorator
To write a new decorator, similar to DataLoader, there is no base class to extend. Instead you just need to follow the prescribed API:
const sty = require('sty');
class MyAnsiDecorator {
// required configure(object config)
configure(config = {}) {
this.config = config;
},
// required decorate(string message)
decorate(message) {
return sty.parse(message);
}
}
This class can either be an npm module or just a local file.
Changes for transport bundle devs
All that is in core. For the actual transport bundle developer they would write the following:
// inside their server-event script, e.g., server-events/telnet-server.js
// current:
const stream = new TelnetStream();
stream.attach(telnetSocket);
// new:
const stream = state.TransportDecoratorRegistry.decorate(TelnetStream);
stream.attach(telnetSocket);