Skip to content

Keyborg instances are overriden when multiple code instances are deployed on the same site #99

@mathis-m

Description

@mathis-m

Issue Description

Keyborg uses a global __keyborg property on the window object to store its core instance and references to all Keyborg instances. When multiple Keyborg instances are created on the same site but from different modules, they override each other because they share the same global property but use different id counters. This happens because each module initializes its own private _lastId variable.

Code Reference

The issue occurs with the module-level counter that generates IDs:

let _lastId = 0;

Each instance uses this counter to create IDs in the constructor + it will register itself using the id on the global state:

keyborg/src/Keyborg.ts

Lines 293 to 302 in 76e5740

private constructor(win: WindowWithKeyborg, props?: KeyborgProps) {
this._id = "k" + ++_lastId;
this._win = win;
const current = win.__keyborg;
if (current) {
this._core = current.core;
current.refs[this._id] = this;
} else {

Impact

This issue causes significant problems in applications with multiple bundles that include Keyborg:

  • When multiple module instances are loaded, none are functioning properly as the override their Keyborg Instances in two way fashion
  • Previously created Keyborg instances stop working correctly
  • Keyboard navigation state tracking becomes inconsistent

Real-world impact: This is a critical issue for Microsoft Dynamics 365, where PCFs (Power Apps Component Framework) come with FluentUI v9 bundled. For example, a standard PCF with FluentUI v9 Combobox component will not indicate keyboard navigation mode when users interact with the component using the keyboard, because the Keyborg instance created by the PCF is overridden by the one created by the out-of-the-box script.

Proposed Solution

As there is already a concept for the global __keyborg property, we could extend it to store the next available ID for Keyborg instances. This way, each Keyborg instance would have a unique ID regardless of which module created it.

The solution would involve:

  1. Storing the last used ID in the global __keyborg object
  2. Using that shared counter instead of module-level _lastId
  3. Ensuring ID generation is synchronized across all Keyborg instances

I am happy to provide a PR for this issue if you agree with the suggested solution or have other ideas to address this problem.

Priority

This issue requires priority attention as it impacts Microsoft Dynamics 365 functionality and any other application that might bundle multiple instances of Keyborg.

Other Concerns

If there will come the time where the core class gets an major update even fix, everything will break.

This is due to the fact that the core instance is a singleton across all modules even if they are not the same version.

See:

keyborg/src/Keyborg.ts

Lines 299 to 308 in 76e5740

if (current) {
this._core = current.core;
current.refs[this._id] = this;
} else {
this._core = new KeyborgCore(win, props);
win.__keyborg = {
core: this._core,
refs: { [this._id]: this },
};
}

What ever the first keyborg instance is will provide the core functionality. This is concerning...

Workaround

I will provide a npm patch in the next days

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions