Skip to content

Environment creation

Protected edited this page Nov 17, 2023 · 4 revisions

Implement a new Environment type to allow Rowboat to connect to a new type of remote service. New Environments should be placed in environment/EnvMyEnvironment.js, where MyEnvironment should be the name of your new Environment type.

All Environments must extend the Environment class. The following is an example barebones Environment that shows the methods you should override (note the environment type being passed to the superclass in the constructor):

import Environment from '../src/Environment.js';

export default class EnvMyEnvironment extends Environment {

    get description() { return "Description of the Environment type."; }

    get params() { return [
        //List of parameters
    ]; }
    
    get defaults() { return {
        //Map of parameter default values
    }; }
    
    constructor(name) {
        super('MyEnvironment', name);
        //Class instance initialization
    }

    connect() {}
    disconnect() {}
    msg(targetid, msg, options) {}
    notice(targetid, msg, options) {}

    idToDisplayName(id) { return null; }
    displayNameToId(displayName) { return null; }
    
    idToMention(id) { return null; }
    
    idIsSecured(id) { return false; }
    idIsAuthenticated(id) { return false; }
    
    listUserIds(channel) { return []; }
    
    listUserRoles(id, channel) { return []; }
    
    channelIdToDisplayName(channelid) { return null; }
    channelIdToType(channelid) { return "regular"; }
    
    roleIdToDisplayName(roleid) { return null; }
    displayNameToRoleId(displayName) { return null; }
    
    normalizeFormatting(text) { return text; }
    applyFormatting(text) { return text; }

}

The input values for these methods assume your Environment understands the following concepts:

Channel: Colloquially also known as a "room". Multiple users can be subscribed to a channel, and all users subscribed to a channel receive messages delivered to that channel.

ID, User ID and Channel ID: A string that uniquely identifies a user or channel within the remote service. The format used by your Environment for IDs can be constructed by you, as long as it's self-consistent.

Role: A role is a virtual qualifier that can be assigned to one or more users. Each user may also have multiple roles. The concept of a role is often used to differentiate the permissions available to a user in a remote service.

Secured and Authenticated: A user is secured when using a secure communication channel to access the remote service. A user is authenticated when their identity - their right to be identified as their User ID - has been reliably verified. It's possible that in certain remote services, visible users are always secured and authenticated; it's also possible that in others, they never are.

Parameters

Parameters can be used for configuring an instance of your Environment type.

Declare parameters supported by the Environment type in the list returned by the params() getter. Each entry in this list is an object with the following format:

{n: "parameterName", d: "parameterDescription"}

Values for declared parameters can be provided in Configuration files and can be accessed in your class using this.param("parameterName").

The defaults() getter returns a map of parameter names to their default values. If a parameter has a default value (even if it's null), it becomes optional, and does not have to be provided by the configuration file stack. Otherwise, it's a required parameter and must be provided by the configuration file stack, or initialization will fail.

Shared modules

Environments can reference one or more shared module instances that are the same for every environment. This is useful when your Environment type creates virtual instances that make use of the same shared remote service client.

To declare a shared module, first override the sharedModules() getter:

    get sharedModules() { return [
        'MySharedModule'
    ]; }

The shared module will be imported from environment/MySharedModule.js and should export a default object to be supplied to each Environment instance. Please note that if you export a class, you must instantiate it yourself, like so (note the new):

export default new class MySharedModule {
    ...
}

To use the shared module in your Environment, you must intercept it by overriding the initialize(opt) method, which is run by Core during startup:

    #mySharedModule = null;

    initialize(opt) {
        if (!super.initialize(opt)) return false;

        this.#mySharedModule = opt.sharedInstances.MySharedModule;

        return true;
    }

Keep in mind that startup will fail if initialize returns false.

Events

NOTE: Standard event callback signatures are pending refactoring.

The main purpose of your Environment is to emit events for Behaviors to listen to. Whenever applicable, you should emit the following standardized events using this.emit('name', arguments...):

  • ('error', error) - An error has occured. The argument is the error message.
  • ('connected', env) - The environment has connected successfully and is now ready to be used. The env argument is the environment instance.
  • ('disconnected', env) - The environment has been disconnected and is no longer usable. The argument is the environment instance.
  • ('message', env, type, message, authorid, channelid, messageObject) - A message was received or seen. type can be an environment-specific message type, but "regular" and "private" are expected. messageObject is an environment-specific object to be delivered to the listener.
  • ('messageSent', env, type, targetid, message) - A message was just sent by this Environment's user account. targetid can be a public or private channel.
  • ('join', env, userid, channelid, info) - A user has become visible (has arrived) in the context of the given channel. info is an environment-specific map.
  • ('part', env, userid, channelid, info) - A user is no longer visible (has departed). info is an environment-specific map.
  • ('gotRole', env, userid, roleid, channelid, ischange) - A user has obtained a role. channelid can be null.
  • ('lostRole', env, userid, roleid, channelid, ischange) - A user has lost a role within the current session. channelid can be null.

You can also emit any amount of Environment-specific events.

Methods to override

From the example at the top, you should override the methods to the base class with their proper implementations:

  • connect() - Establish a connection or session in the remote service.
  • disconnect() - End a connection or session in the remote service.
  • msg(targetid, msg, options) - Deliver a message to the target User or Channel ID. Emit the messageSent event. The options object is environment-specific and optional.
  • notice(targetid, msg, options) - Deliver a notification to the target User or Channel ID. If not supported by the remote service, this should do the same thing as msg.
  • idToDisplayName(id) - Obtain a human-friendly display name for the given user ID.
  • displayNameToId(displayName) - Attempt to obtain a likely ID for the given user display name. Used when humans interacting with the platform are trying to reference a user by name.
  • idToMention(id) - Generate a mention for the usaer with the given ID. If the remote service doesn't support mentions, return the display name.
  • idIsSecured(id) - Return true if the given user ID is connected to the remote service over a secure channel.
  • idIsAuthenticated(id) - Return true if the given user ID has been authenticated with the remote service.
  • listUserIds(channel) - Return a list of all user IDs known to be subscribed with a channel.
  • listUserRoles(id, channel) - Return a list of every role a user has, optionally in the context of a channel.
  • channelIdToDisplayName(channelid) - Returns a display name for the channel with the given ID.
  • channelIdToType(channelid) - Returns the Rowboat type of the channel with the given ID. Same as the message event.
  • roleIdToDisplayName(roleid) - Returns a display name for the role with the given ID.
  • displayNameToRoleId(displayName) - Attempts to return an identifier for a role with the given display name.
  • normalizeFormatting(text) - Converts formatting in a message specific to the remote service to Rowboat formatting. Normalized formatting is Markdown-friendly: *italics* (soft emphasis), **bold** (strong emphasis) and __underlined__.
  • applyFormatting(text) - Converts Rowboat formatting in a message to formatting specific to the remote service.

These methods will be used by Behaviors to interact with the remote service in a cross-Environment friendly manner.

You can implement additional, Environment-specific methods that will be used by Behaviors designed specifically for your Environment type (or to provide optional functionality).

Shutdown and Cleanup handlers

You can register one or more cleanup or shutdown handlers with the Core to be executed when the process ends. Keep in mind disconnect() is not executed automatically, so if any cleanup is necessary, it should be replicated in a cleanup handler.

Much like Shared Modules, to register cleanup and shutdown handlers you must override initialize(opt).

    initialize(opt) {
        if (!super.initialize(opt)) return false;

        opt.pushShutdownHandler((next) => {
            ...
            next();
        });

        opt.pushCleanupHandler(() => {
            ...
        });

        return true;
    }

Shutdown handlers run when the process receives a SIGINT (interruption) signal and are used for graceful interrupt handling. The first registered handler runs immediately, and should call next() to proceed with the shutdown. You can use promises in a shutdown handler, calling next() asynchronously when the Promise fulfills. If you do not call next(), the shutdown is aborted and the platform will continue running.

Cleanup handlers run synchronously when it's determined that the process will end, after shutdown has been completed, in the event of an error or when using process.exit. Promises and timers are no longer supported and will never run at this point. The cleanup process can't be aborted.

Clone this wiki locally