Skip to content
Andreas Aronsson edited this page Jul 24, 2022 · 16 revisions

Streaming Widget Development Wiki

This is a small wiki to describe how to develop for this project, instructions on how to use the widget are here: Streaming Widget Wiki

This will be updated at the same time as the develop branch, so it might actually be ahead of the master branch.

Code Structures

Singletons

The project has a number of modules and states that are initiated in their respective singletons at launch: ModulesSingleton and StatesSingleton.
These can be accessed in other places of the code like so:

const modules = ModulesSingleton.getInstance()
const states = StatesSingleton.getInstance()

Keep in mind to not access other modules in your own module's constructor, as the order of instantiation can change. That said, the singleton is instantiated at the beginning of execution, so any access outside the constructor should in most cases be fine.

Modules

The main way to add functionality to the project is by writing a module. These are usually classes that we instantiate in the modules singleton. There are a few steps to make modules accessible to an end user.

  1. Add a module source file to ./src/modules/, anything in here will get transpiled and then included at runtime.
  2. Write your module, it's fine to have it either as a static class (only static function) or something that needs to be instantiated in the singleton.
  3. If the module is non-static, add a property for it in ./src/modules_singleton.ts, e.g:
    class ModulesSingleton {
        public yourModule = new YourModule()
    }

This should mean your module is now ready to be interacted with by other code.

Actions

To allow for an end user to trigger things in your module, we need to add an action for it, this can then be set up in the config by the user. This is a multi-step process:

  1. Add a new interface for your module by appending ./src/interfaces/iactions.ts, this is what we setup in the event to trigger this action, e.g:
    interface IYourAction {
        // Mandatory parameters
        setupParam1: string
        setupParam2: number
        // Optional parameters
        optionalSetupParam1?: boolean
        optionalSetupParam2?: { [key: string]: string }
    }
  2. Add the property for this action to the main event interface in ./src/interfaces/iactions.ts, meaning IActions, and make it optional, e.g:
    interface IActions {
        /**
         * Optional: My module allows for this and that to happen!
         */
        yourConfig?: IYourConfig
        // or
        yourConfigWithIncrementalRewardSupport?: IYourConfig|IYourConfig[]
    }
  3. Create an action callback builder in ./src/actions.ts, the builder can take a few parameters that are supplied to all actions when executed, e.g:
    public static buildYourCallback(
        config: IYourConfig|undefined,
        key: string // Optional input in case you need to know the key
    ): ITwitchActionCallback|undefined {
        if(config) return {
            tag: '😀',
            description: 'What does this action do?',
            call: (user: IActionUser) => {
                // If your module needs to have an instance
                const modules = ModulesSingleton.getInstance()
                modules.yourModule.yourPublicFunction(singleConfig, user, key) // user and key are additional data that can be useful
    
                // If your module is all static functions
                YourModule.yourPublicStaticFunction(singleConfig)
            }
        }
    }
    There are a few more considerations if your action supports the incremental reward type. It's fairly specialized, but here is an example. This also shows how to use asynchronous functions inside the action callback:
    public static buildYourIncrementalCallback(
        config: IYourConfig|IYourConfig[]|undefined, // Note array type
    ): ITwitchActionCallback|undefined {
        if(config) return {
            tag: '😁',
            description: 'What does this action do?',
            call: async (user: IActionUser, index?: number) => { // Note index
                const singleConfig = Utils.randomOrSpecificFromArray(config, index) // Random if index is undefined
                if(singleConfig) {
                    const modules = ModulesSingleton.getInstance()
                    await modules.yourModule.yourPublicAsyncFunction(singleConfig)
                    // or
                    YourModule.yourPublicStaticFunction(singleConfig)
                }
            }
        }
    }
  4. Finally use your builder to build a callback in Actions.buildActionCallback(), and push your action onto the stack, e.g:
    // The actions come from the triggered IEvent, the key is something to use if you need it.
    // There are more values that can be used in the builder, check others for reference.
    stack.pushIfExists(this.buildYourCallback(actions?.yourConfig, key))
Clone this wiki locally