Code Monkey home page Code Monkey logo

fvtt-lib-wrapper's Introduction

1. FVTT libWrapper

Library for Foundry VTT which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages and making troubleshooting easier.

License Build Release Version (latest) Foundry Version GitHub downloads (latest) Forge Install Base GitHub issues Ko-fi

1.1. Why?

The libWrapper library is intended to be used as a replacement for traditional monkey-patching methods, to make interoperability and troubleshooting easier.

It provides a standardized API, improving consistency and compatibility while reducing the likelihood of mistakes.

Its wrapping mechanisms attempt to detect and prevent conflicts between different packages. When conflicts cannot be prevented, libWrapper aids troubleshooting by notifying the game master of what went wrong, and which packages are responsible.

Traditional monkey-patching libWrapper
Conflict troubleshooting Requires waiting until something goes wrong.
User is responsible for figuring out if an error is caused by a conflict, and which package(s) caused it.
Able to detect most conflicts and warn the user, in many cases automatically upon game launch.
Error Detection None by default. When any error occurs, libWrapper will detect which package(s) caused it (if any), and tell the user.
API None.
Each developer is on their own.
Provides a standard API enforcing best-practices with included error-checking.
Wrapper execution order Package execution order, i.e. random. Customizable.
Developers can request to run first or last, if their module requires it.
Game masters can troubleshoot conflicts by prioritising or deprioritising packages.
Edge cases Each developer must understand the intricacies of Javascript. Handled automatically and transparently.

Note: Images may be out-of-date.

1.2. Installation

1.2.1. As a Module

  1. Copy this link and use it in Foundry's Module Manager to install the Module

    https://github.com/ruipin/fvtt-lib-wrapper/releases/latest/download/module.json

  2. Enable the Module in your World's Module Settings

1.2.2. As a Library

You have multiple options here.

  1. Include the provided shim in your project.

    or

  2. Write your own shim. Please do not make your custom shim available in the global scope.

    or

  3. Trigger a different code path depending on whether libWrapper is installed and active or not. For example:

    if(typeof libWrapper === 'function') {
        /* libWrapper is available in global scope and can be used */
    }
    else {
        /* libWrapper is not available in global scope and can't be used */
    }

    or

  4. Require your users to install this library. One simple example that achieves this is provided below. Reference the more complex example in the provided shim if you prefer a dialog (including an option to dismiss it permanently) instead of a simple notification.

    Hooks.once('ready', () => {
        if(!game.modules.get('lib-wrapper')?.active && game.user.isGM)
            ui.notifications.error("Module XYZ requires the 'libWrapper' module. Please install and activate it.");
    });

    Note that if you choose this option, i.e. require the user to install this library, you should make sure to list libWrapper as a dependency. This can be done by adding one of the following entries to your package's manifest:

    1. Foundry VTT v10 and newer:

      "relationships": {
          "requires": [
              {
                  "id": "lib-wrapper",
                  "type": "module",
                  "compatibility": {
                      "minimum": "1.0.0.0",
                      "verified": "1.12.6.0"
                  }
              }
          ]
      }

      The "compatibility" section and all fields within it are optional, and serve to declare the versions of the dependency which your package requires. This can be useful if you rely on libWrapper features added by newer versions (by using "minimum"), as well as to communicate to the user what version of the library you tested against (by using "verified").

    2. Foundry VTT v9 and older (forward-compatible with v10):

      "dependencies": [
          {
              "name": "lib-wrapper",
              "type": "module"
          }
      ]

If you pick options #2 or #3 and actively recommend to the user to install libWrapper using e.g. a notification, it is a good idea to give the user a way to permanently dismiss said notification. The provided shim does this by having a "Don't remind me again" option in the alert dialog.

Once your package is released, you should consider adding it to the wiki list of Modules using libWrapper. This list can also be used as an additional (unofficial) source of libWrapper usage examples.

1.2.3. As a Contributor

See CONTRIBUTING.md.

1.3. Usage

1.3.1. Summary

In order to wrap a method, you should call the libWrapper.register method during or after the init hook, and provide it with your package ID, the scope of the method you want to override, and a wrapper function. You can also specify the type of wrapper you want in the fourth (optional) parameter:

  • WRAPPER:

    • Use if your wrapper will always continue the chain (i.e. call wrapped).
    • This type has priority over every other type. It should be used whenever possible as it massively reduces the likelihood of conflicts.
    • ⚠ If you use this type but do not call the original function, your wrapper will be automatically unregistered.
  • MIXED (default):

    • Your wrapper will be allowed to decide whether it should continue the chain (i.e. call wrapped) or not.
    • These will always come after WRAPPER-type wrappers. Order is not guaranteed, but conflicts will be auto-detected.
  • OVERRIDE:

    • Use if your wrapper will never continue the chain (i.e. call wrapped). This type has the lowest priority, and will always be called last.
    • If another package already has an OVERRIDE wrapper registered to the same method:
      • If the GM has explicitly given your package priority over the existing one, libWrapper will trigger a libWrapper.OverrideLost hook, and your wrapper will take over.
      • Otherwise, libWrapper will throw a libWrapper.AlreadyOverriddenError exception. This exception can be caught by your package in order to fail gracefully and activate fallback code.

If using WRAPPER or MIXED, the first parameter passed to your wrapper will be the next wrapper in the wrapper chain, which you can use to continue the call.

libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (wrapped, ...args) {
    console.log('Foo.prototype.bar was called');
    // ... do things ...
    let result = wrapped(...args);
    // ... do things ...
    return result;
}, 'MIXED' /* optional, since this is the default type */ );

1.3.2. Common Issues and Pitfalls

1.3.2.1. Not allowed to register wrappers before the init hook.

Due to Foundry limitations, information related to installed packages is not available until the FVTT init hook. As such, libWrapper will wait until then to initialize itself.

Any attempts to register wrappers before then will throw an exception. If using the shim, its libWrapper symbol will be undefined until then.

⚠ Note that while the full library provides the libWrapper.Ready hook, which fires as soon as libWrapper is ready to register wrappers, this hook is not provided by the shim.

1.3.2.2. OVERRIDE wrappers have a different call signature

When using OVERRIDE, wrappers do not receive the next function in the wrapper chain as the first parameter. Make sure to account for this.

libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (...args) { // There is no 'wrapped' parameter in the wrapper signature
    console.log('Foo.prototype.bar was overridden');
    return;
}, 'OVERRIDE');

1.3.2.3. Arrow Functions do not support this

Per the Javascript specification, arrow function syntax ((args) => { body() }) does not bind a this object, and instead keeps whatever this was defined in the declaration scope.

As such, if you use arrow functions to define your wrapper, you will be unable to use the wrapper's this:

libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', (wrapped, ...args) => {
    console.log(this); // -> 'Window'
}, 'MIXED' /* optional, since this is the default type */ );

If you want access to the this object of the wrapped method, you must use the function(args) { body() } syntax:

libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (wrapped, ...args) {
    console.log(this); // -> 'Foo'
}, 'MIXED' /* optional, since this is the default type */ );

1.3.2.4. Using super inside wrappers

Sometimes, it is desired to call a superclass method directly. Traditionally, super would be the right tool to do this. However, due to the specifics of how super works in Javascript it cannot be used outside of the class definition, and therefore does not work inside wrappers.

As a result, to call a superclass method directly you will need to manually find the superclass method definition yourself. This can be done multiple ways, and two examples are provided below.

The examples below assume this is of class ChildClass, that the superclass has the name SuperClass, and that the method we wish to call is superclass_method.

  1. Travel the class hierarchy automatically, using Object.getPrototypeOf:

    Object.getPrototypeOf(ChildClass).prototype.superclass_method.apply(this, args);
  2. Hardcode the class we wish to call:

    SuperClass.prototype.superclass_method.apply(this, args);

The first option should be preferred, as it will work even if the superclass name (SuperClass in this example) changes in a future Foundry update.

1.3.2.5. Patching Mixins

Since FoundryVTT 0.8.x, the core Foundry code makes heavy use of mixins. Since mixins are essentially a function that returns a class, patching the mixin directly is not possible.

Instead, you should patch these methods on the classes that inherit from the mixins.

For example, in the Foundry code we have the following (with irrelevant code stripped):

const CanvasDocumentMixin = Base => class extends ClientDocumentMixin(Base) {
    /* ... */

    _onCreate(data, options, userId) {
        /* ... */
    }

    /* ... */
}

/* ... */

class TileDocument extends CanvasDocumentMixin(foundry.documents.BaseTile) {
    /* ... */
}

If we wanted to patch the method _onCreate which TileDocument inherits from CanvasDocumentMixin(foundry.documents.BaseTile), we could do the following:

libWrapper.register('my-fvtt-package', 'TileDocument.prototype._onCreate', function(wrapped, ...args) {
  console.log("TileDocument.prototype._onCreate called");
  return wrapped(...args);
}, 'WRAPPER');

1.3.3. LibWrapper API

⚠ Anything not documented in this section is not officially supported, and could change or break at any moment without notice.

1.3.3.1. Registering a wrapper

To register a wrapper function, you should call the method libWrapper.register(package_id, target, fn, type):

/**
 * Register a new wrapper.
 * Important: If called before the 'init' hook, this method will fail.
 *
 * In addition to wrapping class methods, there is also support for wrapping methods on specific object instances, as well as class methods inherited from parent classes.
 * However, it is recommended to wrap methods directly in the class that defines them whenever possible, as inheritance/instance wrapping is less thoroughly tested and will incur a performance penalty.
 *
 * Triggers FVTT hook 'libWrapper.Register' when successful.
 *
 * Returns a unique numeric target identifier, which can be used as a replacement for 'target' in future calls to 'libWrapper.register' and 'libWrapper.unregister'.
 *
 * @param {string} package_id  The package identifier, i.e. the 'id' field in your module/system/world's manifest.
 *
 * @param {number|string} target The target identifier, specifying which wrapper should be registered.
 *
 *   This can be either:
 *     1. A unique target identifier obtained from a previous 'libWrapper.register' call.
 *     2. A string containing the path to the function you wish to add the wrapper to, starting at global scope, for example 'SightLayer.prototype.updateToken'.
 *
 *   Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
 *
 *   Since v1.8.0.0, the string path (option #2) can contain string array indexing.
 *   For example, 'CONFIG.Actor.sheetClasses.character["dnd5e.ActorSheet5eCharacter"].cls.prototype._onLongRest' is a valid path.
 *   It is important to note that indexing in libWrapper does not work exactly like in JavaScript:
 *     - The index must be a single string, quoted using the ' or " characters. It does not support e.g. numbers or objects.
 *     - A backslash \ can be used to escape another character so that it loses its special meaning, e.g. quotes i.e. ' and " as well as the character \ itself.
 *
 *   By default, libWrapper searches for normal methods or property getters only. To wrap a property's setter, append '#set' to the name, for example 'SightLayer.prototype.blurDistance#set'.
 *
 * @param {function} fn        Wrapper function. The first argument will be the next function in the chain, except for 'OVERRIDE' wrappers.
 *                             The remaining arguments will correspond to the parameters passed to the wrapped method.
 *
 * @param {string} type        [Optional] The type of the wrapper. Default is 'MIXED'.
 *
 *   The possible types are:
 *
 *   'WRAPPER' / libWrapper.WRAPPER:
 *     Use if your wrapper will *always* continue the chain.
 *     This type has priority over every other type. It should be used whenever possible as it massively reduces the likelihood of conflicts.
 *     Note that the library will auto-detect if you use this type but do not call the original function, and automatically unregister your wrapper.
 *
 *   'MIXED' / libWrapper.MIXED:
 *     Default type. Your wrapper will be allowed to decide whether it continue the chain or not.
 *     These will always come after 'WRAPPER'-type wrappers. Order is not guaranteed, but conflicts will be auto-detected.
 *
 *   'OVERRIDE' / libWrapper.OVERRIDE:
 *     Use if your wrapper will *never* continue the chain. This type has the lowest priority, and will always be called last.
 *     If another package already has an 'OVERRIDE' wrapper registered to the same method, using this type will throw a <libWrapper.ERRORS.package> exception.
 *     Catching this exception should allow you to fail gracefully, and for example warn the user of the conflict.
 *     Note that if the GM has explicitly given your package priority over the existing one, no exception will be thrown and your wrapper will take over.
 *
 * @param {Object} options [Optional] Additional options to libWrapper.
 *
 * @param {boolean} options.chain [Optional] If 'true', the first parameter to 'fn' will be a function object that can be called to continue the chain.
 *   This parameter must be 'true' when registering non-OVERRIDE wrappers.
 *   Default is 'false' if type=='OVERRIDE', otherwise 'true'.
 *   First introduced in v1.3.6.0.
 *
 * @param {string} options.perf_mode [Optional] Selects the preferred performance mode for this wrapper. Default is 'AUTO'.
 *   It will be used if all other wrappers registered on the same target also prefer the same mode, otherwise the default will be used instead.
 *   This option should only be specified with good reason. In most cases, using 'AUTO' in order to allow the GM to choose is the best option.
 *   First introduced in v1.5.0.0.
 *
 *   The possible modes are:
 *
 *   'NORMAL' / libWrapper.PERF_NORMAL:
 *     Enables all conflict detection capabilities provided by libWrapper. Slower than 'FAST'.
 *     Useful if wrapping a method commonly modified by other packages, to ensure most issues are detected.
 *     In most other cases, this mode is not recommended and 'AUTO' should be used instead.
 *
 *   'FAST' / libWrapper.PERF_FAST:
 *     Disables some conflict detection capabilities provided by libWrapper, in exchange for performance. Faster than 'NORMAL'.
 *     Will guarantee wrapper call order and per-package prioritization, but fewer conflicts will be detectable.
 *     This performance mode will result in comparable performance to traditional non-libWrapper wrapping methods.
 *     Useful if wrapping a method called repeatedly in a tight loop, for example 'WallsLayer.testWall'.
 *     In most other cases, this mode is not recommended and 'AUTO' should be used instead.
 *
 *   'AUTO' / libWrapper.PERF_AUTO:
 *     Default performance mode. If unsure, choose this mode.
 *     Will allow the GM to choose which performance mode to use.
 *     Equivalent to 'FAST' when the libWrapper 'High-Performance Mode' setting is enabled by the GM, otherwise 'NORMAL'.
 *
 * @param {any[]} options.bind [Optional] An array of parameters that should be passed to 'fn'.
 *
 *   This allows avoiding an extra function call, for instance:
 *     libWrapper.register(PACKAGE_ID, "foo", function(wrapped, ...args) { return someFunction.call(this, wrapped, "foo", "bar", ...args) });
 *   becomes
 *     libWrapper.register(PACKAGE_ID, "foo", someFunction, "WRAPPER", {bind: ["foo", "bar"]});
 *
 *   First introduced in v1.12.0.0.
 *
 * @returns {number} Unique numeric 'target' identifier which can be used in future 'libWrapper.register' and 'libWrapper.unregister' calls.
 *   Added in v1.11.0.0.
 */
static register(package_id, target, fn, type='MIXED', options={}) { /* ... */ }

See the usage example above.

1.3.3.2. Unregistering a wrapper

To unregister a wrapper function, you should call the method libWrapper.unregister(package_id, target).

/**
 * Unregister an existing wrapper.
 *
 * Triggers FVTT hook 'libWrapper.Unregister' when successful.
 *
 * @param {string} package_id     The package identifier, i.e. the 'id' field in your module/system/world's manifest.
 *
 * @param {number|string} target  The target identifier, specifying which wrapper should be unregistered.
 *
 *   This can be either:
 *     1. A unique target identifier obtained from a previous 'libWrapper.register' call. This is the recommended option.
 *     2. A string containing the path to the function you wish to remove the wrapper from, starting at global scope, with the same syntax as the 'target' parameter to 'libWrapper.register'.
 *
 *   It is recommended to use option #1 if possible, in order to guard against the case where the class or object at the given path is no longer the same as when `libWrapper.register' was called.
 *
 *   Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
 *
 * @param {function} fail         [Optional] If true, this method will throw an exception if it fails to find the method to unwrap. Default is 'true'.
 */
static unregister(package_id, target, fail=true) { /* ... */ }

1.3.3.3. Unregister all wrappers for a given package

To clear all wrapper functions belonging to a given package, you should call the method libWrapper.unregister_all(package_id).

/**
 * Unregister all wrappers created by a given package.
 *
 * Triggers FVTT hook 'libWrapper.UnregisterAll' when successful.
 *
 * @param {string} package_id  The package identifier, i.e. the 'id' field in your module/system/world's manifest.
 */
static unregister_all(package_id) { /* ... */ }

1.3.3.4. Ignore conflicts matching specific filters

To ask libWrapper to ignore specific conflicts when detected, instead of warning the user, you should call the method libWrapper.ignore_conflicts(package_id, ignore_ids, targets).

/**
 * Ignore conflicts matching specific filters when detected, instead of warning the user.
 *
 * This can be used when there are conflict warnings that are known not to cause any issues, but are unable to be resolved.
 * Conflicts will be ignored if they involve both 'package_id' and one of 'ignore_ids', and relate to one of 'targets'.
 *
 * Note that the user can still see which detected conflicts were ignored, by toggling "Show ignored conflicts" in the "Conflicts" tab in the libWrapper settings.
 *
 * First introduced in v1.7.0.0.
 *
 * @param {string}            package_id  The package identifier, i.e. the 'id' field in your module/system/world's manifest. This will be the package that owns this ignore entry.
 *
 * @param {(string|string[])} ignore_ids  Other package ID(s) with which conflicts should be ignored.
 *
 * @param {(string|string[])} targets     Target(s) for which conflicts should be ignored, corresponding to the 'target' parameter to 'libWrapper.register'.
 *   This method does not accept the unique target identifiers returned by 'libWrapper.register'.
 *
 * @param {Object} options [Optional] Additional options to libWrapper.
 *
 * @param {boolean} options.ignore_errors  [Optional] If 'true', will also ignore confirmed conflicts (i.e. errors), rather than only potential conflicts (i.e. warnings).
 *   Be careful when setting this to 'true', as confirmed conflicts are almost certainly something the user should be made aware of.
 *   Defaults to 'false'.
 */
static ignore_conflicts(package_id, ignore_ids, targets, options={}) { /* ... */ }

1.3.3.5. Library Versioning

This library follows Semantic Versioning, with two custom fields SUFFIX and META. These are used to track manifest-only changes (e.g. when compatibleCoreVersion increases) and track release meta-data (e.g. release candidates and unstable versions) respectively. See below for examples.

The version string will always have format <MAJOR>.<MINOR>.<PATCH>.<SUFFIX><META>.

The MAJOR, MINOR, PATCH and SUFFIX fields will always be integers.

The META field is always a string, although it will often be empty. The - character between SUFFIX and META is optional if META is empty or does not start with a digit. This last field META is unnecessary when comparing versions, as a change to this field will always cause one of the other fields to be incremented.

A few (non-exhaustive) examples of valid version strings and the corresponding [MAJOR, MINOR, PATCH, SUFFIX, META]:

libWrapper.version libWrapper.versions
1.2.3.4 [1, 2, 3, 4, '']
1.3.4.0rc [1, 3, 4, 0, 'rc']
2.1.0.2a [2, 1, 0, 2, 'a']
3.4.5.6dev [3, 4, 5, 6, 'dev']

The libWrapper object provides a few properties and methods to query or react to the library version:

// Properties
/**
 * Get libWrapper version
 * @returns {string}  libWrapper version in string form, i.e. "<MAJOR>.<MINOR>.<PATCH>.<SUFFIX><META>"
 */
static get version() { /* ... */ }

/**
 * Get libWrapper version
 * @returns {[number,number,number,number,string]}  libWrapper version in array form, i.e. [<MAJOR>, <MINOR>, <PATCH>, <SUFFIX>, <META>]
 */
static get versions() { /* ... */ }

/**
 * Get the Git version identifier.
 * @returns {string}  Git version identifier, usually 'HEAD' or the commit hash.
 */
static get git_version() { /* ... */ };


// Methods
/**
 * Test for a minimum libWrapper version.
 * First introduced in v1.4.0.0.
 *
 * @param {number} major   Minimum major version
 * @param {number} minor   [Optional] Minimum minor version. Default is 0.
 * @param {number} patch   [Optional] Minimum patch version. Default is 0.
 * @param {number} suffix  [Optional] Minimum suffix version. Default is 0. First introduced in v1.5.2.0.
 * @returns {boolean}      Returns true if the libWrapper version is at least the queried version, otherwise false.
 */
static version_at_least(major, minor=0, patch=0, suffix=0) { /* ... */ }
1.3.3.5.1. Testing for a specific libWrapper version

Sometimes you might wish to alert the user when an old version is detected, for example when you use functionality introduced in more recent versions.

To test for libWrapper v1.4.0.0 or higher, the simplest way is to use version_at_least:

if(libWrapper.version_at_least?.(major, minor, patch, suffix)) {
    // libWrapper is at least major.minor.patch.suffix
}
else {
    // libWrapper is older than major.minor.patch.suffix
}

The arguments minor, patch and suffix are optional. Note the usage of ?. to ensure this works (and is falsy) before v1.4.0.0.

If you wish to detect versions below v1.4.0.0, you should use versions instead:

const [lwmajor, lwminor, lwpatch, lwsuffix] = libWrapper.versions;
if(
    lwmajor > major || (lwmajor == major && (
        lwminor > minor || (lwminor == minor && (
            lwpatch > patch || (lwpatch == patch && lwsuffix >= suffix)
        ))
    ))
) {
    // libWrapper is at least major.minor.patch.suffix
}
else {
    // libWrapper is older than major.minor.patch.suffix
}

1.3.3.6. Fallback / Polyfill detection

To detect whether the libWrapper object contains the full library or a fallback/polyfill implementation (e.g. the shim), you can check libWrapper.is_fallback.

The library module will set this property to false, while fallback/polyfill implementations will set it to true.

/**
 * @returns {boolean}  The real libWrapper module will always return false. Fallback implementations (e.g. poly-fill / shim) should return true.
 */
static get is_fallback() { /* ... */ }

1.3.3.7. Exceptions

Since v1.2.0.0, various custom exception classes are used by libWrapper, and available in the global libWrapper object.

  • LibWrapperError:

    • Base class for libWrapper exceptions.
  • LibWrapperInternalError extends LibWrapperError:

    • Internal LibWrapper error, usually indicating something is broken with the libWrapper library.
    • Public fields:
      • package_id: Package ID which triggered the error, if any.
  • LibWrapperPackageError extends LibWrapperError:

    • Error caused by a package external to libWrapper. These usually indicate conflicts, usage errors, or out-dated packages.
    • Public fields:
      • package_id: Package ID which triggered the error.
  • LibWrapperAlreadyOverriddenError extends LibWrapperError:

    • Thrown when a libWrapper.register call with type='OVERRIDE' fails because another OVERRIDE wrapper is already registered.
    • Public fields:
      • package_id: Package ID which failed to register the OVERRIDE wrapper.
      • conflicting_id: Package ID which already has a registered OVERRIDE wrapper.
      • target: Wrapper target (the target parameter to libWrapper.register).
  • LibWrapperInvalidWrapperChainError extends LibWrapperError:

    • Thrown when a wrapper tries to call the next method in the wrapper chain, but this call is invalid. This can occur, for example, if this call happens after the wrapper has already returned and all associated promises have resolved.
    • Public fields:
      • package_id: Package ID which triggered the error, if any.

These are available both with and without the LibWrapper prefix, for example libWrapper.Error and libWrapper.LibWrapperError are equivalent and return the same exception class.

1.3.3.8. Hooks

Since v1.4.0.0, the libWrapper library triggers Hooks for various events, listed below:

  • libWrapper.Ready:

    • Triggered when libWrapper is ready to register wrappers. This will happen shortly before the FVTT init hook.
    • No Parameters.
  • libWrapper.Register:

    • Triggered when a libWrapper.register call completes successfully.
    • Parameters:
      • 1: Package ID whose wrapper is being registered (the package_id parameter to libWrapper.register).
      • 2: Wrapper target path (the target parameter to libWrapper.register when it is a string, otherwise the first parameter provided by any module when registering a wrapper to the same method).
      • 3: Wrapper type (the type parameter to libWrapper.register).
      • 4: Options object (the options parameter to libWrapper.register).
      • 5: Wrapper ID (the return value of libWrapper.register).
  • libWrapper.Unregister:

    • Triggered when a libWrapper.unregister call completes successfully.
    • Parameters:
      • 1: Package ID whose wrapper is being unregistered (the package_id parameter to libWrapper.unregister).
      • 2: Wrapper target (the target parameter to libWrapper.unregister when it is a string, otherwise the first parameter provided by any module when registering a wrapper to the same method).
      • 3: Wrapper ID (the return value of libWrapper.Register).
  • libWrapper.UnregisterAll:

    • Triggered when a libWrapper.unregister_all call completes successfully.
    • Parameters:
      • 1: Package ID whose wrappers are being unregistered (the package_id parameter to libWrapper.unregister_all).
  • libWrapper.ConflictDetected:

    • Triggered when a conflict is detected.
    • Parameters:
      • 1: Package ID which triggered the conflict, or «unknown» if unknown.
      • 2: Conflicting package ID.
      • 3: Wrapper name (first target parameter provided by any module when registering a wrapper to the same method).
      • 4: List of all unique target strings provided by modules when registering a wrapper to the same method.
    • If this hook returns false, the user will not be notified of this conflict.
  • libWrapper.OverrideLost:

    • Triggered when an OVERRIDE wrapper is replaced by a higher-priority wrapper.
    • Parameters:
      • 1: Existing package ID whose wrapper is being unregistered.
      • 2: New package ID whose wrapper is being registered.
      • 3: Wrapper name (first target parameter provided by any module when registering a wrapper to the same method).
      • 4: List of all unique target strings provided by modules when registering a wrapper to the same method.
    • If this hook returns false, this event will not be treated as a conflict.

1.3.3.9. Enumerations

Since v1.9.0.0, libWrapper defines a couple of enumeration objects that can be passed to the libWrapper API methods, instead of using strings.

For example, instead of using 'OVERRIDE' in the libWrapper.register call, one could instead use libWrapper.OVERRIDE:

libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (...args) {
    /* ... */
}, libWrapper.OVERRIDE /* instead of 'OVERRIDE' */);

A full list of the enumeration values provided by libWrapper follows:

static get WRAPPER()  { /* ... */ };
static get MIXED()    { /* ... */ };
static get OVERRIDE() { /* ... */ };

static get PERF_NORMAL() { /* ... */ };
static get PERF_AUTO()   { /* ... */ };
static get PERF_FAST()   { /* ... */ };

1.3.3.10. Examples

A list of packages using libWrapper, which can be used as further examples, can be found in the wiki page Modules using libWrapper.

1.3.4. Using libWrapper inside a System

The libWrapper library has official support for all types of packages, including systems.

However, it is recommended that you read through the warnings and recommendations in SYSTEMS.md before you use it inside a system.

1.3.5. Compatibility Shim

The shim.js file in this repository can be used to avoid a hard dependency on libWrapper.

See the respective documentation in shim/SHIM.md.

1.4. Support

As with any piece of software, you might sometimes encounter issues with libWrapper that are not already answered above. This section covers what you can do to find support.

1.4.1. Module-specific Support

When libWrapper notifies you of an error, it will usually let you know whether the issue is caused by a specific module or by libWrapper itself.

Many modules have support channels set up by their developers. If libWrapper warns you about a specific module, and you are aware of such a support channel, you should use it.

Most libWrapper errors are not caused by libWrapper itself, but instead by a module that uses it. Reporting these issues to the libWrapper team directly is a waste of time, as we will not be able to help. These issues will simply be closed as "invalid".

1.4.2. Community Support

The easiest way to find support when there are no module-specific support channels is to ask the community.

The largest community-provided support channels are:

1.4.3. LibWrapper Support

Do not open a support ticket using the link below unless you are seeing an internal libWrapper error or are a package developer. We also do not provide support for packages that promote or otherwise endorse piracy. Your issue will be closed as invalid if you do not fulfill these requirements.

If you encounter an internal libWrapper error, or are a package developer looking for support (i.e. bug reports, feature requests, questions, etc), you may get in touch by opening a new issue on the libWrapper issue tracker. It is usually a good idea to search the existing issues first in case yours has already been answered before.

If your support request relates to an error, please describe with as much detail as possible the error you are seeing, and what you have already done to troubleshoot it. Providing a step-by-step description of how to reproduce it or a snippet of code that triggers the issue is especially welcome, and will ensure you get an answer as fast as possible.

fvtt-lib-wrapper's People

Contributors

brothersharper avatar dependabot[bot] avatar git-gor avatar mclemente avatar ruipin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

fvtt-lib-wrapper's Issues

Latest update conflicts with Scene Clicker

Having this module activated with Scene Clicker prevents Actor sheets from opening from the sidebar.

Not sure if this is a Scene Clicker issue or libWrapper but I narrowed down the issue only persists if I have both activated and just started after latest lib wrapper update...

Full vs Shim compatibility

Hi,

I'm the author of Scene Packer and currently include your shim but not a hard dependency.

I've been asked whether it's possible to remove the popup dialog that's bundled in the shim. I know it's possible, but I want to know what the differences currently are between the full module and the shim version. I can't see an issue or wiki entry that lists the differences and only one issue from several months ago (#3) that discusses differences.

My current thought is to use the shim that you provide, but remove the popup dialog, therefore using the full module if it's enabled but not prompting people if it isn't. What functionality would be lost with this approach though?

v1.4.0.0 has caused serious breakages to many modules.

i have alot of modules such as custom hotbar/midi qol/ automated animations etc etc that require this module as a dependancy its also happening with multi level tokens. where im having a constant flashing token with an error due to the libwrapper. please fix this asap. i need these running for my games.

Getting error when importing an item

Receiving this error whenever I try to import an item. Screen shot for ease.
image

VM6936:7 Foundry VTT | Error thrown in hooked function
_call @ VM6936:7
callAll @ foundry.js:153
callback @ foundry.js:8866
(anonymous) @ foundry.js:8805
_handleUpdateDocuments @ foundry.js:8805
_updateDocuments @ foundry.js:8693
async function (async)
_updateDocuments @ foundry.js:8684
update @ backend.mjs:154
async function (async)
update @ backend.mjs:151
updateDocuments @ document.mjs:365
🎁call_wrapped @ libWrapper-wrapper.js:448
updateDocuments @ ItemContainer.js:77
🎁call_wrapper @ libWrapper-wrapper.js:557
🎁CONFIG.Item.documentClass.updateDocuments#0 @ libWrapper-wrapper.js:144
update @ document.mjs:447
🎁call_wrapped @ libWrapper-wrapper.js:448
_update @ ItemContainer.js:150
🎁call_wrapper @ libWrapper-wrapper.js:557
🎁CONFIG.Item.documentClass.prototype.update#0 @ libWrapper-wrapper.js:144
importFromJSON @ foundry.js:9646
(anonymous) @ foundry.js:9671
Promise.then (async)
callback @ foundry.js:9671
submit @ foundry.js:20351
_onClickButton @ foundry.js:20314
dispatch @ jquery.min.js:2
v.handle @ jquery.min.js:2
VM6936:8 TypeError: Cannot read property 'id' of null
at delayedSort (init.js:114)
at init.js:150
at Function._call (eval at (libWrapper-errors.js:249), :4:14)
at Function.callAll (foundry.js:153)
at ClientDatabaseBackend.callback (foundry.js:8866)
at foundry.js:8805
at Array.map ()
at ClientDatabaseBackend._handleUpdateDocuments (foundry.js:8805)
at ClientDatabaseBackend._updateDocuments (foundry.js:8693)
at async Function.updateDocuments (document.mjs:365)
_call @ VM6936:8
callAll @ foundry.js:153
callback @ foundry.js:8866
(anonymous) @ foundry.js:8805
_handleUpdateDocuments @ foundry.js:8805
_updateDocuments @ foundry.js:8693
async function (async)
_updateDocuments @ foundry.js:8684
update @ backend.mjs:154
async function (async)
update @ backend.mjs:151
updateDocuments @ document.mjs:365
🎁call_wrapped @ libWrapper-wrapper.js:448
updateDocuments @ ItemContainer.js:77
🎁call_wrapper @ libWrapper-wrapper.js:557
🎁CONFIG.Item.documentClass.updateDocuments#0 @ libWrapper-wrapper.js:144
update @ document.mjs:447
🎁call_wrapped @ libWrapper-wrapper.js:448
_update @ ItemContainer.js:150
🎁call_wrapper @ libWrapper-wrapper.js:557
🎁CONFIG.Item.documentClass.prototype.update#0 @ libWrapper-wrapper.js:144
importFromJSON @ foundry.js:9646
(anonymous) @ foundry.js:9671
Promise.then (async)
callback @ foundry.js:9671
submit @ foundry.js:20351
_onClickButton @ foundry.js:20314
dispatch @ jquery.min.js:2
v.handle @ jquery.min.js:2
foundry.js:9735 The Document#entity property has been deprecated in favor of Document#documentName. Support will be removed in 0.9.0

How to reference game system defined classes

How do I wrap a game system specific class's method. I would like to wrap ActorSheet5e _prepareArmorClassAttribution.
I tried going through CONFIG.Actor.sheetclasses;

CONFIG.Actor.sheetClasses.character["dnd5e.ActorSheet5eCharacter"].cls.prototype._prepareArmorClassAttribution

which points to the correct method, but libwrapper rejects the string

[BUG] libWrapper.ignore_conflicts not ignoring "did not chain wrapper" / "replacing OVERRIDE" conflicts

Confirm you have read the above
Appears to be a problem with how libWrapper is handling the lib wrapper.ignore_conflicts method. I am using that method to ignore a potential conflict, but I am still getting a warning message regarding that very conflict.

Describe the bug
The libRuler module overrides Ruler.prototype.moveToken with this call to libWrapper:
libWrapper.register("libruler", "Ruler.prototype.moveToken", libRulerMoveToken, "OVERRIDE");

This is an override because libRuler basically breaks up the moveToken function into parts that are easier for modules to wrap or override.

I am working on code for the Drag Ruler module that wraps this function:
libWrapper.register("drag-ruler", "Ruler.prototype.moveToken", dragRulerMoveToken, "MIXED");

It is a MIXED wrap because Drag Ruler intercepts certain key commands, like spacebar, and does not call moveToken in that circumstance. Otherwise, it will call the wrap function for moveToken.

I am getting an occasional warning from libWrapper about the potential conflict because "drag-ruler did not chain the wrapper." So I put this ignore code in Drag Ruler:
libWrapper.ignore_conflicts("drag-ruler", "libruler", "Ruler.prototype.moveToken");

After doing so, the console reports the following:

libWrapper: Wrapped 'Ruler.prototype.moveToken'. libWrapper-wrapper.js:347:2
libWrapper: Registered a wrapper for 'Ruler.prototype.moveToken' by module 'libruler' with type OVERRIDE. libWrapper-api.js:516:3
libWrapper: Registered a wrapper for 'Ruler.prototype.moveToken' by module 'drag-ruler' with type MIXED. libWrapper-api.js:516:3
libWrapper: Ignoring conflicts involving module 'drag-ruler' and [module 'libruler'] for targets [Ruler.prototype.moveToken]. libWrapper-api.js:631:3

But the console still reports the following warning (which also shows up as a notification in the UI):

libWrapper: Potential conflict detected between module 'drag-ruler' and module 'libruler'.
Module 'drag-ruler' did not chain the wrapper for 'Ruler.prototype.moveToken'. libWrapper-notifications.js:67:10

To Reproduce
I can reproduce this in my system by logging in and then dragging the token and trying to create a waypoint. It would be difficult to reproduce from libWrapper alone, because it is dependent on work-in-progress Drag Ruler code. But if it helps, I could show you where that code is in my git repository. Or happy to help test solutions.

Expected behavior
I would expect that the conflict warning no longer appear once libWrapper.ignore_conflicts is called.

Technical Details (please complete the following information):

  • LibWrapper Version 1.9.0
  • FoundryVTT Version 0.8.8
  • Browser & Version Firefox 91.0.1

Libwrapper ReferenceError: ace is not defined

Hi, I get the following errors after logging in FoundryVTT in my world:

FoundryVTT: 0.7.10
DnD5e: 1.
Libwrapper: 1.6.0

VM755:8 ReferenceError: ace is not defined
    at editor.js:5
    at Array.forEach (<anonymous>)
    at editor.js:5
    at Function._call (eval at <anonymous> (libWrapper-errors.js:249), <anonymous>:4:14)
    at Function.callAll (foundry.js:2456)
    at Game.initialize (foundry.js:6457)
    at z.🎁call_wrapped [as call_wrapped] (libWrapper-wrapper.js:448)
    at Game.🎁libWrapperInit (libWrapper-api.js:521)
    at Game.🎁Game.prototype.initialize#0 (libWrapper-wrapper.js:142)
    at window.addEventListener.once (foundry.js:7449)
_call @ VM755:8
quick-insert.ts:68 Uncaught (in promise) ReferenceError: KeybindLib is not defined
    at quick-insert.ts:68
    at Function._call (eval at <anonymous> (libWrapper-errors.js:249), <anonymous>:4:14)
    at Function.callAll (foundry.js:2456)
    at Game.initialize (foundry.js:6457)
    at z.🎁call_wrapped [as call_wrapped] (libWrapper-wrapper.js:448)
    at Game.🎁libWrapperInit (libWrapper-api.js:521)
    at Game.🎁Game.prototype.initialize#0 (libWrapper-wrapper.js:142)
    at window.addEventListener.once (foundry.js:7449)

Active module list:
about-time--v0.8.4;
ActiveAuras--v0.2.11;
animated-spell-effects--v0.6.5;
blood-n-guts--v0.8.7;
calendar-weather--v3.1.2;
coloredeffects--v0.0.7;
combat-utility-belt--v1.4.0;
CommunityLighting--v0.3.3;
compendium-folders--v2.2.44;
conditional-visibility--v0.1.4;
condition-automation--v3.0.0;
blind-roll-skills--v0.7.0;
custom-hotbar--v2.1.0;
custom-nameplates--v1.0.2;
Custom-Token-Animations--v0.1.20;
darkraven-games-soundscapes-free--v1.0.1;
deprecated-modules--v1.1.3;
dice-so-nice--v3.3.4;
dice-calculator--v0.6.8;
dnd5e-helpers--v2.0.3;
drag-ruler--v1.7.2;
Dynamic-Effects-SRD--v4.1.13;
dae--v0.2.62;
dynamic-illumination--v0.9.0;
EasyTable--v1.3.2;
easy-target--v2.11;
forien-copy-environment--v1.1.1;
forien-scene-navigator--v0.1.6;
foundry_community_macros--v0.40;
foundry_community_tables--v0.13;
free-loot-tavern-compendium--v1.9.1;
fxmaster--v1.0.3;
gm-notes--v0.4;
gm-screen--v2.4.3;
healthEstimate--v2.4.0;
launchmacro--v1.3.3;
illandril-chat-enhancements--v1.1.5;
illandril-hotbar-uses--v2.3.3;
illandril-token-hud-scale--v1.3.3;
image-hover--v1.1.7;
itemacro--vv1.5.1;
jb2a_patreon--v0.2.0;
colorsettings--v2.5.9;
_chatcommands--v1.3.3;
lib-df-hotkeys--v2.3.4;
lib-wrapper--v1.6.1.0;
LockView--v1.4.10;
long-rest-hd-healing--v2.2.0;
macroeditor--v1.0.7;
maestro--v0.7.4;
magicitems--v2.0.8;
_mathjs--v7.5.1-fvtt2;
max-crit--v0.1.7;
michaelghelfi--v1.2;
midi-qol--v0.3.103;
monsterblock--v2.4.7;
multilevel-tokens--v1.4.2;
Next-Up--v0.0.59;
perfect-vision--v1.9.4;
permission_viewer--v0.8.5;
pings--v1.2.11;
quick-insert--v2.4.7;
raise-my-hand--v1.1.0;
scene-transitions--v0.1.3;
settings-extender--v1.1.5;
SharedVision--v1.0.4;
alt5e--v1.5.2;
socketlib--v1.0.6;
_sortablejs--v1.10.2-fvtt1;
SoundBoard--v1.5.1;
tension-pool--v0.0.30;
Tetra-Cube-Importer--v1.1.0;
furnace--vv2.6.0;
tidy-ui_game-settings--v0.1.23;
times-up--v0.1.5;
token-action-hud--v1.0.22;
token-attacher--v4.0.2;
tokenmagic--v0.5.0;
rolladvantage-token-stamp-2-foundry--v1.1.2;
token-vision-animation-world--v1.0.0;
torch--v1.1.4;
trigger-happy--v0.8.2;
turnAlert--v1.5.1;
VoiceActor--v0.0.9;

Add before/after properties to allow modules to specify default prioritization

As it stands, modules are unable to signal a desired prioritization other than "WRAPPER", "MIXED", and "OVERRIDE".

A suggestion that has come my way is to add optional before and after constraints that will be used to decide on default prioritization. These would be lists of module IDs (or a single module ID), which would run by default before/after the given wrapper.

Example:

libWrapper.register("module-a", "Foo.prototype.bar", "MIXED", {before: "module-b", after: ["module-c", "module-d"]});

Optionally, we could have the dictionary be the third parameter (when it is not a string, for backward compatibility), and have the wrapper type be one of the dictionary values:

libWrapper.register("module-a", "Foo.prototype.bar", {type: "MIXED", before: "module-b", after: ["module-c", "module-d"]});

The user could then be warned if these constraints conflict (including when it's because the users themselves changed prioritization).

Remove private code from public libWrapper scope

Currently, the global libWrapper class exposes a lot of underlying code (methods that start with _) that probably shouldn't be available in global scope.

If I have some time, it might be useful to move these methods off to a private scope that is not exposed to the world. Additionally, it might make sense for the release versions to minify any method name starting in _.

"Statistics collection is disabled" for players

Steps to reproduce:

In foundry settings - Permission configuration page give Player or Trusted player group the Modify configuration settings permission.
Login as (trusted) player.
Open libWrapper settings.

Observed:

User can't modify libWrapper settings.Priorities and Conflicts tabs are empty. "Statistics collection is disabled" message is displayed.

Expected:

User, who is given access to libWrapper settings, should be able to change them.

SyntaxError: Invalid regular expression: invalid group specifier name

Foundry 0.7.9, lib wrapper 1.5.3.0, dnd5e 1.2.4.
Server: docker image on VM running Ubuntu
Browser: Safari Technical Preview, Release 123 (Safari 14.2, WebKit 16612.1.7.10), on macOS 11.3.1

Disclaimer

Yes, I know Foundry doesn't actively support Safari. But please, hear me out... Foundry runs well on Safari Technical Preview, and on macOS, Safari is dramatically faster than Chrome or Firefox. I mean, sometimes double the framerate on an older laptop. And this particular issue, I think, involves the lib wrapper shim, so it breaks a lot of modules.

Issue
When using lib wrapper alone, or using other modules that use the default lib wrapper shim code, I see this error in the console:

SyntaxError: Invalid regular expression: invalid group specifier name

The result is that lib wrapper doesn't load, nor do other modules that use that shim. Nor do modules that depend on libwrapper. So this error has rather large consequences to getting Foundry to work in Safari.

Proposed Solution

I think this error is due to the fact that Safari does not accept negative or positive lookahead regex expressions. See this StackOverflow question or this one for examples.

Specifically, this regex in shim.js may need to be changed in order to work in Safari:
const MODULE_ID = ((import.meta?.url ?? Error().stack)?.match(/(?<=\/)modules\/.+(?=\/)/i)??[])[0]?.split('/')?.find(n => n && game.modules.has(n));

If that can be changed to not use the positive lookbehind, then hopefully lib wrapper will load on Safari, and other modules can adopt the change as well in their shim.

Thanks!

Port all test cases to CallOrderChecker

Commit 26f0970 implements checker CallOrderChecker which allows automatic checking of wrapper call order, including detailed validation of argument passing, return values, and this. It also helps make unit tests much more readable.

Before: https://github.com/ruipin/fvtt-lib-wrapper/blob/8cddf9f593e8587739fa3f9269ca7b55a775b07e/tests/test_wrapper.js
After: https://github.com/ruipin/fvtt-lib-wrapper/blob/26f097024a37b2b088514117809a6b3f75dd231b/tests/test_wrapper.js

These changes allowed me to easily find bug #13.

As such, the remaining test cases (e.g. test_lib.js) should be ported to use this checker.

While Using LibWrapper, Foundry 0.8.6, and the Forge I am getting Depreciation Errors

In the console view I am seeing the following issues, which seem to be adding a slight delay to the start up:

  • The Document#_id property is deprecated in favor of Document#id or Document#data#_id. Support will be removed in 0.9.0
  • You are calling PlaceableObject#setFlag which has been deprecated in favor of Document#setFlag. Support will be removed in 0.9.0
  • You are retrieving the value of CONFIG.Item.entityClass which has been renamed to CONFIG.Item.documentClass. Support for the old configuration value will be removed in 0.9.0
  • PF2e | Failed to execute onBeforePrepareData on rule element [object Object]. TypeError: Cannot read property 'type' of null
  • PF2E | Unknown rule element PF2E.RuleElement.FlatModifer

I also exported the console for the session:

Console_Export_Forge_Foundry.log

Async wrappers can throw

class Foo {
    async bar() {
        return 0;
    }
}

libWrapper.register("some-module", "Foo.prototype.bar", async function (wrapped) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    const a = await wrapped();
    return a + 1;
});

const foo = new Foo();

await foo.bar();
Uncaught libWrapper: This wrapper function is no longer valid, and must not be called. This is most likely caused by an issue in the 'Foo.prototype.bar' wrapper registered by module 'some-module'.

It's probably necessary to add a new suffix to support asynchronous functions, because we can't know whether wrapped is called asynchronously or the function simply returns a promise but wrapped is called synchronously.

libWrapper.register("some-module", "Foo.prototype.bar#async", ...)

Allow OVERRIDE wrappers to chain.

The module "Better Rolls" has an interesting use case. It is, by default, a full override. As such, it uses "OVERRIDE". However, if the caller passes a specific parameter, it wishes to call the original method.

Because OVERRIDE by default doesn't provide the wrapped chain object, they are forced to use private APIs to access the _wrapped method instead. In addition to being unsupported, this will soon become impossible due to the work for #16.

In the original API design, OVERRIDE didn't provide the wrapped chain object on purpose. This was to avoid people using OVERRIDE just because they wanted to be the last module called, even when they weren't truly overriding a method. However, I failed to realise that there is a very rare use case where such a module might wish to call the original method.

An optional solution that does not compromise the API's design goals and maintains backward compatibility is to take an optional chain parameter that, if true, provides the wrapped chain object regardless of the wrapper type:

libWrapper.register("module-a", "Foo.prototype.bar", "OVERRIDE", {chain: true});

Optionally, the third parameter could be replaced by the dictionary (only when not a string, for backwards compatibility), e.g.:

libWrapper.register("module-a", "Foo.prototype.bar", {type: "OVERRIDE", chain: true});

Support wrapping constructors

Right now, libWrapper does not support wrapping constructors.

Some investigation should be done as to whether this is actually feasible, and if so we should implement this.

Something like patchwork.js might serve as inspiration.

Trigger hooks when OVERRIDE wrappers get overridden.

If an existing OVERRIDE wrapper gets taken over by a different module because they have priority, right now modules are not notified.

It might make sense to add an option API to allow modules to set a callback that should be fired when this event happens.

This could use the Hooks system, e.g. a hook libWrapper.OverrideLost

Might make sense to add this also for similar events, e.g. libWrapper.ConflictDetected.

Add standardised patching framework

Inspired by some modules like Mess which patch methods using a regex, I have been wondering about standardising an API to do this officially supported by libWrapper, and that attempts to handle possible issues such as conflicts.

This API could be e.g.

libWrapper.patch("module-id", "path.to.method", /match/, "replace");

and for ease-of-use, also support

libWrapper.patch("module-id", "path.to.method",
  [/match1/, "replace1"],
  [/match2/, "replace2"],
  ...,
  [/matchN/, "replaceN"]
);

Each pair would behave exactly like a call to String.prototype.replace, so would also support replacement methods, etc.

libWrapper would then be responsible for working out which method to patch (including, if necessary, modifying a prior patch), and automatically setting up a corresponding OVERRIDE.

  • If there's already an override, this method would fail, unless the given module has a higher priority, in which case the existing override gets discarded (and a conflict is signalled).
  • Someone registering an OVERRIDE that is higher priority than all of the modules that set up a patch will cause the patch to be discarded, similar to the current behaviour when there are two OVERRIDE conflicts.
  • If libWrapper cannot resolve the function to source code, it throws an exception.
  • If any individual patch "pair" leaves the source code unchanged, libWrapper throws an exception. A third parameter can be used to signal that patches are not required.
    • The exception is if the patch is being applied on top of an already patched method, and the current module is higher priority than the existing module(s). In such a case, the existing patches are dropped, and the new module takes over.
  • If the final source code is unchanged from the original (i.e. none of the patch pairs applied successfuly), libWrapper throws an exception.
  • If the final source code fails to parse, libWrapper throws an exception.
  • Patches (or to be more exact, the corresponding OVERRIDE) cannot be unregistered.
  • The 🎁 emoji will be prefixed to the patched function names, to make it obvious in the call stack the method has been modified.

This would mean that two modules patching the same method, as long as their regexes don't overlap, would still be compatible.

Modules would be responsible for catching the exceptions if they require fall-back behaviour. As usual, uncaught exceptions will be raised to the user as errors.

The shim could be given a naive fallback implementation, that just does toString(), replace, and then overwrites the original.

If an OVERRIDE wrapper fails because of another module, the module will not appear in the UI for prioritization.

The documentation states:

- If another module already has an `OVERRIDE` wrapper registered to the same method:
        - If the GM has explicitly given your module priority over the existing one, your wrapper will take over.
        - Otherwise, libWrapper will throw a `libWrapper.AlreadyOverriddenError` exception. This exception can be caught by your module in order to fail gracefully and activate fallback code.

However, because failed OVERRIDE wrappers do not add the module to the UI, if the only wrapper by a given module is an OVERRIDE, it will be impossible to prioritize it without first disabling the conflicting module.

This should be fixed, so that a failed OVERRIDE wrapper still adds the module to the UI.

Essentially, instead of generating the list of modules by looking at active wrappers, modules should be added to the list as soon as they attempt to register a wrapper - even if the registration later fails.

As a work-around, modules can register a dummy wrapper to make sure they're added to the modules list, e.g.:

class _dummy {
  function target() {
  }
}
globalThis._dummy_MyModule_wrapper_target = _dummy;

libWrapper.register('my-module', '_dummy_MyModule_wrapper_target.prototype.target', ()=>{});

Thanks to Cris#6864 on Discord for reporting this.

Error on fallback implementation

I am currently trying to implement this library and I am running into following problem when this module isn't active:

TypeError: Cannot read property 'value' of undefined
    at Function.register (shim.js:34)
    at PlayerData.registerMouseWheel (player-data.js:133)
    at patch_ruler (patch-ruler.js:18)
    at difficult-terrain.js:8
    at Function._call (foundry.js:2239)
    at Function.call (foundry.js:2224)
    at Canvas.draw (foundry.js:10911)
    at async Scene.view (foundry.js:29232)
    at async Game.initializeCanvas (foundry.js:5942)
    at async Game.setupGame (foundry.js:5824)

while my code looks like this:

    registerMouseWheel() {
        let self = this;
        libWrapper.register(MODULE_ID, 'canvas.activeLayer._onMouseWheel', function (wrapped, e) {
            if (!self.rulerArray.some(value => value.waypoints.length > 0))
                return wrapped(e);

            if (Date.now() - self.lastRegisteredMouseWheel < self.gameSettings.interval)
                return

            self.lastRegisteredMouseWheel = Date.now();
            if (e.deltaY < 0) {
                self.setTerrainMultiplier(self.gameSettings.increment)
                self.updateRuler(e);
            } else {
                self.setTerrainMultiplier(-self.gameSettings.increment)
                self.updateRuler(e);
            }
        }, 'MIXED');

I have no problem when the lib is activated but I would expect that this would still work, even when no priority is supported.

Full code if needed:
https://github.com/Nordiii/difficultterrain/tree/feature/libwrapper_support

Thanks for your time.

Manually wrapping an instance when the prototype was wrapped by libWrapper causes an infinite loop.

ilcom94 on Discord was trying to wrap the longRest method with libWrapper, and noticed that he was seeing an infinite loop. This did not happen without libWrapper. After some debugging, he tracked this down to an interaction with the module Magic Item, which does not use libWrapper.

With the Magic Items module enabled, the infinite loop can be reproduced using e.g. the following example code:

libWrapper.register("some-module", "CONFIG.Actor.entityClass.prototype.longRest", 
async function(wrapped, args={}) {
    console.log("longRest pre", args);
    // can access/modify args here if you want, e.g. args.dialog or args.chat or args.newDay
    let data = await wrapped(args);
    console.log("longRest post", data);
    return data;
});

Investigation so far

It looks like the Magic Item module wraps instances directly, using the following code:

this.actor.longRest = this.longRest(this.actor.longRest, this);

When libWrapper wraps the prototype's longRest, it creates a Handler object which triggers the libWrapper Wrapper functionality.

Because the actor by default doesn't have an instance-specific longRest object, then this.actor.longRest reads that Handler object. Magic Items then assigns its wrapper to this.actor.longRest, creating an instance-specific member instead of trying to replace the class prototype.

Usually, if someone tries to re-assign a Handler object, libWrapper will detect this, redirect said handler to the original method, and re-wrap the method after the assignment to ensure the libWrapper wrappers have primacy. However, because in this situation the Handler object isn't re-assigned, but instead the instance's member is modified directly, this code never triggers.

When libWrapper finishes the call chain and goes to call the original method, it will trigger the instance-specific method which contains the Magic Items wrapper, which then triggers libWrapper, and this repeats all over again.

Assuming this is the true cause, this issue will only occur in the case where someone manually wraps an inherited class or instance directly, while libWrapper was used to wrap the parent class or prototype.

Some further testing needs to be done to ensure this is the actual issue.

Solution

Some investigation/brain-storming needs to be done to decide how to solve this.

Assuming the above hypothesis is correct, we might not wish to trigger the instance-specific method after our wrappers if we have wrapped the prototype. We also need to consider what to do with regards to the Handler read by Magic Items - if someone were to wrap the prototype directly, Magic Items would be pointed towards the original method directly, skipping libWrapper, which might not be desirable.

Meanwhile, we need to ensure this situation is part of the test suite.

Recommended way to use libWrapper with systems

What is the recommended way to incorporate libWrapper into a system? I'd like to use it, and also have it available for any modules that also have a dependency on libWrapper.

I've seen that installing some modules, Foundry will prompt the user to install any dependent modules. Is this automation, or must be supported by code or configuration in the system? How to do this?

Allow module devs and users to ignore specific conflicts

Right now, libWrapper only has global toggles to disable all notifications related to detected conflicts.

Sometimes, conflicts are expected and known by a module dev or the user, and they do not want to see the notifications for them. However, disabling the notifications runs the risk of the user not being told of new conflicts after e.g. updating various modules.

  • The backend needs to be updated to support ignoring specific conflicts.

  • There should still be a toggle in the "Conflicts" pane to show all conflicts detected, even those that were ignored. This would allow to debug issues where ignores are too broad.

  • There should be an API that allows module devs to choose conflicts that the user should not be notified of.

  • There should be a new config pane that allows the user to choose specific notifications they do not want to see again.

Improve user experience and clarity when errors are reported by libWrapper

A lot of people seem to confuse libWrapper errors caused by modules (where libWrapper gives a message like libWrapper: Error detected in module 'some-module-id') as an error caused by libWrapper.

This results in a considerable amount of unrelated complaints or invalid bug reports. Explaining repeatedly that what they're complaining about is not a libWrapper bug takes some time, and unfortunately this is not a rare issue, with some more extreme cases of people DMing me on Discord directly somewhat aggressively.

It is necessary to improve the library such that it is clearer which errors are actually caused by libWrapper, and which ones are simply detected by libWrapper.

These misunderstandings seem to be caused mostly by two different issues:

  1. Error messages are too technical. Users see the libWrapper prefix on an error and assign blame immediately, since they do not have the technical knowledge to understand what the error message means.

    Possible improvements here are (in no particular order):

    • Document what kind of support we provide, and point anything else to either module-specific support channels, or the community Foundry support channels (Discord #modules-troubleshooting / Reddit).

      • "About" page inside the libWrapper settings dialog.
      • README.md
      • Github issue template
        • Explain what kind of information we require to triage an issue.
    • Explain everywhere above that we do not provide support for errors caused by modules that promote or endorse piracy (LibWrapper will work fine, but do not expect support here)

    • Improve error messages

      • Use module names (instead of IDs) for any user-facing error messages.
      • Remove libWrapper prefix from any error message not caused by libWrapper.
      • Add This is *not* a libWrapper bug to a prominent place of the console messages for any issue not caused by libWrapper.
      • If the module provides an issue tracker in its Manifest, provide a link to this in the JS Console error message.
      • Provide a link to the package page in foundryvtt.com, if available.
      • If the error is a failure to find a method to wrap, explicitly state It is likely this module has not been updated for <Foundry version X.Y.Z>. as extra user-facing information.
      • For errors not caused by libWrapper:
        • Notification Message: <Module Name>: Error detected in module. <extra user facing information, if any>
        • Exception message (displayed in the JS console):
          <Notification Message>
          This issue should be reported to the corresponding module developer, it is *not* a libWrapper bug.
          
          Find information about this module here: <`url` entry in manifest; or URL to module page on foundryvtt.com> 
          Report this issue here: <URL to issue tracker for module>. [NOTE: only if `bugs` entry in manifest>
          Ask the community for support at:  [NOTE: Only if no bug tracker above]
          - Discord #modules-troubleshooting channel (https://discord.gg/foundryvtt)
          - Reddit (https://www.reddit.com/r/FoundryVTT)
          
          == Technical Details for Developers:
          Detected by libWrapper.
          Package ID= <Module ID>
          Error= <technical error message>
          
      • For errors caused by libWrapper:
        • Notification Message: libWrapper: Internal error detected. <extra user facing information, if any>
        • Exception message (displayed in the JS console):
          <Notification Message>
          
          Find information about libWrapper here: <libWrapper github homepage> 
          Report this issue here: <URL to libWrapper issue tracker>.
          
          == Technical Details for Developers:
          Caused by libWrapper
          Related Module ID= <Related Module ID>
          Error= <technical error message>
          
    • Show an error message dialog on errors

      • This dialog has the same information as the JS console
      • With hypertext links
      • With an expandable and copy+pastable "Technical Details" section
      • Possibility to disable these messages (and get only notifications)
      • Foundry version updates forcefully re-enable these messages
      • If bug reporter is installed, use it (depends on League-of-Foundry-Developers/bug-reporter#103)
  2. The error listeners (see error-listeners.js) are present on every single stack trace containing a hook being called, or an Application being rendered. Many people seem to misunderstand this as indicating libWrapper is causing the errors. Possible improvements here are:

    • Rename the file to listeners.js, which is a less "culprit-sounding" name.
    • Do not prefix libWrapper- to the sourceMap name of the error listeners file.
    • Wait until this is implemented by Foundry, which will remove the need for these listeners to show up in the stack frame.

More suggestions are welcome.

Add high-performance wrapper type.

While negligible in most cases, libWrapper will add some overhead to wrapper calls compared to traditional wrappers.

In some cases, e.g. testWall, methods get called thousands of times in a loop, meaning that said performance hit actually becomes noticeable.

For these cases, it could make sense to add a wrapper type which does static dispatch. This type would avoid having to set up the libWrapper context, and forgo most of the libWrapper safeties, keeping only basic conflict checking and module prioritisation. Everything necessary for this wrapper should be resolved at wrapping time, similar to how the shim does it.

It's breaking the auto crit damage from mid qol

FVTT 0.7.9 (node)
DnD5e System 1.2.0
libWrapper 1.0.5.3
Compatibily erro with Midi QOL 0.3.43
I tried to reinstall both modules and without success, I know it depends on the other module and the problem may be in it, if it is the case I will report to the author of mid qol but I thought it opportune to warn.
Thank you

Add localization

Is your feature request related to a problem, e.g. with a module? Please describe.
Currently there is no way to localize text for other languages. All settings of libWrapper are displayed in English invariant of what the games language is set to. This can be problematic for people that are not fluent in the English language.

Describe the solution you'd like
Implement foundries default solution for translation using the i18n framework. Specify translation and keys in dedicated files, each containing the translation for a specific local.

Describe alternatives you've considered
There really is no alternative. It would simple improve accessibility for the user.

Additional context
I do understand why libWrapper itself does not prioritize translations, however I would still emphasize that the amount of work to localize the project is minimal and actual translations can be done by the community.

Improve callstack

Right now, whenever libWrapper appears in the callstack, it is usually quite cryptic.

For example:

layer.targetObjects is not a function
    at Canvas._onDragLeftDrop (foundry.js:10723)
    at O._call_wrapped (wrapper.js:298)
    at O.call_wrapper (wrapper.js:389)
    at Canvas.canvasOnDragLeftDrop (main.js:127)
    at O.call_wrapper (wrapper.js:421)
    at Canvas.🎁@handler0(Canvas.prototype._onDragLeftDrop) (wrapper.js:133)
    at MouseInteractionManager.callback (foundry.js:36392)
    at MouseInteractionManager._handleDragDrop (foundry.js:36767)
    at MouseInteractionManager._handleMouseUp (foundry.js:36737)
    at e.a.emit (index.js:181)

One can see that the libWrapper code is in a wrapper.js file.

This should be improved - the wrapper.js file should be renamed to libWrapper-wrapper.js. The file lib-wrapper.js should be renamed libWrapper-lib.js. These are the two files that will often show up in call-stacks, and thus we should make sure they contain the full library name.

In addition, the Canvas.🎁@handler0(Canvas.prototype._onDragLeftDrop) function name should probably be shortened, for example to Canvas.🎁Canvas.prototype._onDragLeftDrop#0.

Lastly, it makes sense to also use Function.displayName for the browsers that support it (i.e. Firefox).

Notify GM of potential module conflicts, with a setting to disable

Right now, any potential module conflicts detected by libWrapper are shown only in the "Conflicts" tab in the settings menu, as well as in the JS console.

Due to the number of people that don't realise this functionality exists, I believe we should change this so that by default, conflicts are notified to the GM whenever they occur, and the GM can choose to disable this in the settings menu.

Ideally, we'd warn only once per detected conflict. A more advanced implementation of this feature could allow the GM to ignore conflicts between specific modules one-by-one.

Handle toString() on a wrapped method

Some modules, like Mess, attempt to patch existing methods by converting them to string first using toString(), applying a regex, and then replacing the original method.

Unfortunately, this is completely incompatible with any method wrapped by libWrapper, as toString() returns the source code of the libWrapper handler.

If possible, it might be a good idea to override the toString() method of the libWrapper handler function objects, and outright throw an exception, or if possible return the original method's toString() result.

Improve call stack for Hooks

Due to Foundry swallowing exceptions that occur inside hooks, libWrapper hooks Hooks._call to catch any unhandled exceptions.

However, this leads to very ugly call stacks any time a hook is triggered, which leads some people to mistakenly believe that an issue is caused by libWrapper, when libWrapper was not involved.

Ideally, we should improve this so that the hooks callstack isn't polluted by libWrapper. Multiple possibilities exist, e.g.:

  1. Do not decorate the Hooks._call wrapper.
  2. Override the Hooks._call wrapper, instead of wrapping it, reducing the number of libWrapper entries in the callstack, and avoid rethrowing.
  3. Move this hook to a different file not called errors.js, e.g. hooks-listener.js.

Measure coverage

Currently, the test suite uses tape but does not measure code coverage.

We could potentially switch to node-tap which has native coverage support, although it seems it depends on nyc which does not support code coverage for ESModules yet.

Alternatively, we could try using c8 for coverage, while staying on tape.

wrapping property overrides

I have an override like this

Object.defineProperty(Token.prototype, 'inCombat', {get: inCombat})

function inCombat() {
    const combat = ui.combat.combat;
    if (!combat) return false;
    const combatants = combat.getCombatantByToken(this.id);
    return combatants.some(c => c !== undefined);
}

but I can't find an example of that kind of overrides in the documentation. Is wrapping this kind of overrides possible in the current version?

Add support for systems, world scripts and macros

Currently, world scripts and macros are unable to use libWrapper as they do not have a module ID.

It might be a good idea to come up with a special ID that can be used for those cases. For example, "SCRIPTS" or 0.

In addition, systems can't use libWrapper either. While they usually won't need it, since they run first, might make sense to add support in case someone wants to patch things after init.

Add support for wrapping generators

Because generator functions are synctactic sugar around a Generator factory, trying to wrap a generator the naive way will trigger a libWrapper exception:

game.generatorFn = function*(...args) {
  yield args[0];
  yield args[1];
  yield args[2];
};

libWrapper.register('package_id', 'game.generatorFn',  function*(wrapped, ...args) {
  const gen = wrapped(...args);
  yield gen.next().value+1;
  yield gen.next().value+1;
  yield gen.next().value+1;
}, "WRAPPER");

console.log(game.generatorFn(1).next());
// <- The wrapper for 'game.generatorFn' registered by module 'package_id' with type WRAPPER did not chain the call to the next wrapper, which breaks a libWrapper API requirement. This wrapper will be unregistered.

A workaround is to divide the wrapper into two parts, a factory, and a generator:

game.generatorFn = function*(...args) {
  yield args[0];
  yield args[1];
  yield args[2];
};

function* generatorFn_wrapped(wrapped, ...args) {
  yield wrapped.next().value+1;
  yield wrapped.next().value+1;
  yield wrapped.next().value+1;
}

libWrapper.register('package_id', 'game.generatorFn',  function(wrapped, ...args) {
  return generatorFn_wrapped(wrapped(...args), ...args);
}, "WRAPPER");

console.log(game.generatorFn(1,2,3).next().value);
// <- 2

It might be a good idea to have libWrapper do this automatically when it detects the wrapper function is a generator. When libWrapper.register detects that the wrapper function is a generator, it should automatically create a corresponding factory, and register that instead, similar to above.

To detect whether a function is a generator, the following StackOverflow thread is useful:
https://stackoverflow.com/questions/21559365/how-to-identify-an-es6-generator

Support chaining more than once

Issue

Currently, libWrapper explicitly prevents modules from chaining more than once:

libWrapper.register('my-fvtt-module', 'SightLayer.prototype.updateToken', function (wrapped, ...args) {
    wrapped(...args); // Works
    wrapped(...args); // Throws 'libWrapper: This wrapper function has already been called, and must not be called twice.'
}, 'MIXED');

This was implemented to avoid the extra complexities that come with managing the call chain as well as some libWrapper internal limitations (which have since been resolved, and thus are no longer the limiting factor).

Possible solutions

Option 1

It would be very simple to implement a situation where modules higher in the call chain do not see the multiple chaining, i.e. if we have 3 modules A, B, and C wrapping the same method in this order, and B chains twice:

A -> B -> C1 -> original
       -> C2 -> original

The biggest advantage of this option is that it is simply emulating the way that pure Javascript wrappers already work. This means there is no surprise for the user of the library, and things "just work".

This scales well enough if two modules chain twice. For example, if both A and B were to chain twice:

A -> B1 -> C11 -> original
        -> C12 -> original
  -> B2 -> C21 -> original
        -> C22 -> original

However, this could easily break module A or C. A might need to see every call to the original method, while C might not expect to see the same call twice. As such, it could end up causing some difficult-to-debug compatibility issues.

Option 2

It could be argued that the following would be more correct behaviour:

A1 -> B -> C1 -> original
        -> A2 -> C2 -> original

This would ensure A sees all calls to the original method, including those caused by multiple chaining.
However, I suspect this actually makes it easier to break module A, as it breaches the principle of least surprise due to going against the way pure Javascript wrappers work.

This option also gets more complex if two modules chain twice. For example, if both A and B were to chain twice:

A1 -> B1 -> C111 -> original
         -> A2 -> C121 -> original
               -> C122 -> original
   -> B2 -> C211 -> original
         -> A3 -> C221 -> original
               -> C222 -> original

Option 3

Another possibility is to create a new type of override (e.g. MULTICHAIN) which must be unique (similar to OVERRIDE), and always comes between MIXED and OVERRIDE, i.e. it is always the penultimate call in the chain.

This override type would be allowed to chain the call twice, since there will no longer be modules between it and the final call in the chain, as such removing the need to manage the correct call chain.

The only significant disadvantage of this option is that, due to the uniqueness requirement, it prevents two modules from chaining the same wrapper more than once.

Conclusion

After going through these options, I am tempted to go with Option 1 since it is the most simplistic, and works most similar to pure Javascript wrappers. Option 3 is also tempting, but I think the strict limitation of only one module chaining twice is hard to justify purely on compatibility grounds.

Shim and module code function differently for OVERRIDE type

In the shim code, functions are called like this:

obj[fn_name] = function() { return fn.call(this, original, ...arguments); };

In the module code they're called like this:

result = fn.call(obj, next_fn, ...args);

except if they're OVERRIDE type, in which case they're called like this:

return fn.apply(obj, args);

This breaks the behavior of shimmed override functions (e.g. in my module, ZoomPanOptions) when the module exists. The next_fn argument is not passed.

Add API to query priorities

It might be useful for modules to be able to query the current priorities, or request a list of modules that is higher/lower priority than them.

Unsure how to handle unprioritized modules, though.

Inconsistent call order when wrappers are inherited.

Right now, if we have the following class hierarchy:

  • Class A implements method foo, wrapped by method WrapperA using libWrapper.
  • Class B extends A implements method foo which calls super.foo() (i.e. A.foo()).

By default:

  • A.foo() calls WrapperA() -> A.foo().
  • B.foo() calls B.foo() -> WrapperA() -> A.foo().

If we wrap B.foo with method WrapperB using libWrapper, the situation changes:

  • A.foo() calls WrapperA() -> A.foo() (no change).
  • B.foo() calls WrapperB() -> WrapperA() -> B.foo() -> A.foo().

Notice that WrapperA() and B.foo() swapped position in the B.foo() call order.

This is because libWrapper currently gives priority to any inherited wrappers (i.e. WrapperA()), and only once they're exhausted will it consider calling the wrapped method (i.e. B.foo()). Since before, B.foo() was not controlled by libWrapper, it would not check for inherited wrappers.

This is an inconsistency which in my opinion breaches the principle of least surprise, since this is not how class inheritance nor traditional JS wrappers would work.

Specifically, it means libWrapper can accidentally bubble up wrappers from base classes all the way up to child classes, with which the wrapper might be incompatible.

This prioritization should be removed, such that:

  • A.foo() calls WrapperA() -> A.foo() (no change).
  • B.foo() calls WrapperB() -> B.foo() -> WrapperA() -> A.foo().

The test cases which detected this issue will need to be updated with the correct call order once this bug is fixed:

// Instantiate E
let e = new E();
await chkr.call(e, 'x', ['E:Orig'], 'e.Orig');
// Wrapping E's prototype will work
// NOTE: This is inconsistent... Compare 'e.E:1' with 'e.Orig' above. 'A:1' only shows up once E is wrapped by libWrapper.
// This is because currently, inherited wrappers get priority over the original method of the child class. See github issue #13.
wrap_front(E.prototype, 'x', chkr.gen_wr('E:1'));
await chkr.call(e, 'x', ['E:1','A:1','E:Orig'], 'e.E:1');
// Instantiate F
// Using the 'super' construct will work, even if the inherited method is wrapped
let f = new F();
await chkr.call(f, 'x', ['F:Orig','A:1','A:Orig'], 'f.Orig');
// Using the 'super' construct will work, even if the method itself is wrapped
// NOTE: As before, there is an inconsistency here due to inherited wrappers getting priority. Perhaps this should be ['F:1','F:Orig','A:1','A:Orig']?
// This is because currently, inherited wrappers get priority over the original method of the child class. See github issue #13.
wrap_front(F.prototype, 'x', chkr.gen_wr('F:1'));
await chkr.call(f, 'x', ['F:1','A:1','F:Orig','A:Orig'], 'f.F:1');

Shim does not support instance-specific / inherited-method wrapping.

Since v1.0.4, the full libWrapper library officially supports instance-specific as well as inherited-method wrapping.

Unfortunately, adding this to the shim would be quite a bit of work, and as such the shim does not support this as of now.

For example, the following will work with the full library but throw an exception with the shim:

class A {
	x() {
		console.log("inside A.x");
	}
}

class B extends A {
}

libWrapper.register(MODULE_ID, 'A.prototype.x', function(original) {
	console.log("inside A.prototype.x wrapper");
});
// <- Works with both the full library and the shim

libWrapper.register(MODULE_ID, 'B.prototype.x', function(original) {
	console.log("inside B.prototype.x wrapper");
});
// <- If using the shim, throws exception 'Uncaught libWrapper Shim: "B.prototype.x" does not exist or could not be found.'
//    If using the full library, works fine.

globalThis.a = new A();
libWrapper.register(MODULE_ID, 'a.x', function(original) {
	console.log("inside a.x wrapper");
});
// <- If using the shim, throws exception 'Uncaught libWrapper Shim: "a.x" does not exist or could not be found.'
//    If using the full library, works fine.

It is worth brainstorming whether this could be implemented in a simple way that would be appropriate for the shim.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.