All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

738 lines
37 KiB

1 year ago
  1. # 1. FVTT libWrapper
  2. Library for [Foundry VTT](https://foundryvtt.com/) which provides package developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other packages.
  3. [![License](https://img.shields.io/github/license/ruipin/fvtt-lib-wrapper)](LICENSE)
  4. [![Build Release](https://github.com/ruipin/fvtt-lib-wrapper/workflows/Build%20Release/badge.svg)](https://github.com/ruipin/fvtt-lib-wrapper/releases/latest)
  5. [![Version (latest)](https://img.shields.io/github/v/release/ruipin/fvtt-lib-wrapper)](https://github.com/ruipin/fvtt-lib-wrapper/releases/latest)
  6. [![Foundry Version](https://img.shields.io/badge/dynamic/json.svg?url=https://github.com/ruipin/fvtt-lib-wrapper/releases/latest/download/module.json&label=Foundry%20Version&query=$.compatibleCoreVersion&colorB=blueviolet)](https://github.com/ruipin/fvtt-lib-wrapper/releases/latest)
  7. [![GitHub downloads (latest)](https://img.shields.io/badge/dynamic/json?label=Downloads@latest&query=assets[?(@.name.includes('zip'))].download_count&url=https://api.github.com/repos/ruipin/fvtt-lib-wrapper/releases/latest&color=green)](https://github.com/ruipin/fvtt-lib-wrapper/releases/latest)
  8. [![Forge Install Base](https://img.shields.io/badge/dynamic/json?label=Forge%20Install%20Base&query=package.installs&suffix=%&url=https://forge-vtt.com/api/bazaar/package/lib-wrapper&colorB=brightgreen)](https://forge-vtt.com/)
  9. [![GitHub issues](https://img.shields.io/github/issues-raw/ruipin/fvtt-lib-wrapper)](https://github.com/ruipin/fvtt-lib-wrapper/issues)
  10. [![Ko-fi](https://img.shields.io/badge/-buy%20me%20a%20coffee-%23FF5E5B?logo=Ko-fi&logoColor=white)](https://ko-fi.com/ruipin)
  11. - [1. FVTT libWrapper](#1-fvtt-libwrapper)
  12. - [1.1. Why?](#11-why)
  13. - [1.2. Installation](#12-installation)
  14. - [1.2.1. As a Module](#121-as-a-module)
  15. - [1.2.2. As a Library](#122-as-a-library)
  16. - [1.2.3. As a Contributor](#123-as-a-contributor)
  17. - [1.3. Usage](#13-usage)
  18. - [1.3.1. Summary](#131-summary)
  19. - [1.3.2. Common Issues and Pitfalls](#132-common-issues-and-pitfalls)
  20. - [1.3.2.1. Not allowed to register wrappers before the `init` hook.](#1321-not-allowed-to-register-wrappers-before-the-init-hook)
  21. - [1.3.2.2. OVERRIDE wrappers have a different call signature](#1322-override-wrappers-have-a-different-call-signature)
  22. - [1.3.2.3. Arrow Functions do not support `this`](#1323-arrow-functions-do-not-support-this)
  23. - [1.3.2.4. Using `super` inside wrappers](#1324-using-super-inside-wrappers)
  24. - [1.3.2.5. Patching Mixins](#1325-patching-mixins)
  25. - [1.3.3. LibWrapper API](#133-libwrapper-api)
  26. - [1.3.3.1. Registering a wrapper](#1331-registering-a-wrapper)
  27. - [1.3.3.2. Unregistering a wrapper](#1332-unregistering-a-wrapper)
  28. - [1.3.3.3. Unregister all wrappers for a given package](#1333-unregister-all-wrappers-for-a-given-package)
  29. - [1.3.3.4. Ignore conflicts matching specific filters](#1334-ignore-conflicts-matching-specific-filters)
  30. - [1.3.3.5. Library Versioning](#1335-library-versioning)
  31. - [1.3.3.5.1. Testing for a specific libWrapper version](#13351-testing-for-a-specific-libwrapper-version)
  32. - [1.3.3.6. Fallback / Polyfill detection](#1336-fallback--polyfill-detection)
  33. - [1.3.3.7. Exceptions](#1337-exceptions)
  34. - [1.3.3.8. Hooks](#1338-hooks)
  35. - [1.3.3.9. Enumerations](#1339-enumerations)
  36. - [1.3.3.10. Examples](#13310-examples)
  37. - [1.3.4. Using libWrapper inside a System](#134-using-libwrapper-inside-a-system)
  38. - [1.3.5. Compatibility Shim](#135-compatibility-shim)
  39. - [1.4. Support](#14-support)
  40. - [1.4.1. Module-specific Support](#141-module-specific-support)
  41. - [1.4.2. Community Support](#142-community-support)
  42. - [1.4.3. LibWrapper Support](#143-libwrapper-support)
  43. ## 1.1. Why?
  44. One of the biggest causes of incompatibility between packages is them patching the same method, breaking each other. This module attempts to improve this situation, and also provide package developers with a flexible and easy-to-use API to wrap/monkey-patch core Foundry VTT code.
  45. As a bonus, it provides the Game Master with package conflict detection, as well as the possibility of prioritizing and/or deprioritizing certain packages, which can help resolve conflicts if they do arise.
  46. <img src="https://raw.githubusercontent.com/ruipin/fvtt-lib-wrapper/7cb19d4def1d5ebf84f4df5753f8e48ecfc1523c/example_priorities.png" width="200">
  47. <img src="https://raw.githubusercontent.com/ruipin/fvtt-lib-wrapper/7cb19d4def1d5ebf84f4df5753f8e48ecfc1523c/example_conflicts.png" width="200">
  48. <img src="https://raw.githubusercontent.com/ruipin/fvtt-lib-wrapper/7cb19d4def1d5ebf84f4df5753f8e48ecfc1523c/example_active_wrappers.png" width="200">
  49. <sup>Note: Images may be out-of-date.</sup>
  50. ## 1.2. Installation
  51. ### 1.2.1. As a Module
  52. 1. Copy this link and use it in Foundry's Module Manager to install the Module
  53. > https://github.com/ruipin/fvtt-lib-wrapper/releases/latest/download/module.json
  54. 2. Enable the Module in your World's Module Settings
  55. ### 1.2.2. As a Library
  56. You have multiple options here.
  57. 1. Include the provided [shim](#135-compatibility-shim) in your project.
  58. or
  59. 2. Write your own shim. **Please do not make your custom shim available in the global scope.**
  60. or
  61. 3. Trigger a different code path depending on whether libWrapper is installed and active or not. For example:
  62. ```javascript
  63. if(typeof libWrapper === 'function') {
  64. /* libWrapper is available in global scope and can be used */
  65. }
  66. else {
  67. /* libWrapper is not available in global scope and can't be used */
  68. }
  69. ```
  70. or
  71. 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](#135-compatibility-shim) if you prefer a dialog (including an option to dismiss it permanently) instead of a simple notification.
  72. ```javascript
  73. Hooks.once('ready', () => {
  74. if(!game.modules.get('lib-wrapper')?.active && game.user.isGM)
  75. ui.notifications.error("Module XYZ requires the 'libWrapper' module. Please install and activate it.");
  76. });
  77. ```
  78. 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:
  79. 1. Foundry VTT v10 and newer:
  80. ```javascript
  81. "relationships": {
  82. "requires": [
  83. {
  84. "id": "lib-wrapper",
  85. "type": "module",
  86. "compatibility": {
  87. "minimum": "1.0.0.0",
  88. "verified": "1.12.6.0"
  89. }
  90. }
  91. ]
  92. }
  93. ```
  94. 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"`).
  95. 2. Foundry VTT v9 and older (forward-compatible with v10):
  96. ```javascript
  97. "dependencies": [
  98. {
  99. "name": "lib-wrapper",
  100. "type": "module"
  101. }
  102. ]
  103. ```
  104. 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](#135-compatibility-shim) does this by having a "Don't remind me again" option in the alert dialog.
  105. Once your package is released, you should consider adding it to the wiki list of [Modules using libWrapper](https://github.com/ruipin/fvtt-lib-wrapper/wiki/Modules-using-libWrapper). This list can also be used as an additional (unofficial) source of libWrapper usage examples.
  106. ### 1.2.3. As a Contributor
  107. See [CONTRIBUTING.md](CONTRIBUTING.md).
  108. ## 1.3. Usage
  109. ### 1.3.1. Summary
  110. 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.
  111. You can also specify the type of wrapper you want in the fourth (optional) parameter:
  112. - `WRAPPER`:
  113. - Use if your wrapper will *always* continue the chain (i.e. call `wrapped`).
  114. - This type has priority over every other type. It should be used whenever possible as it massively reduces the likelihood of conflicts.
  115. - ⚠ If you use this type but do not call the original function, your wrapper will be automatically unregistered.
  116. - `MIXED` (default):
  117. - Your wrapper will be allowed to decide whether it should continue the chain (i.e. call `wrapped`) or not.
  118. - These will always come after `WRAPPER`-type wrappers. Order is not guaranteed, but conflicts will be auto-detected.
  119. - `OVERRIDE`:
  120. - 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.
  121. - If another package already has an `OVERRIDE` wrapper registered to the same method:
  122. - 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.
  123. - 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.
  124. 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.
  125. ```javascript
  126. libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (wrapped, ...args) {
  127. console.log('Foo.prototype.bar was called');
  128. // ... do things ...
  129. let result = wrapped(...args);
  130. // ... do things ...
  131. return result;
  132. }, 'MIXED' /* optional, since this is the default type */ );
  133. ```
  134. ### 1.3.2. Common Issues and Pitfalls
  135. #### 1.3.2.1. Not allowed to register wrappers before the `init` hook.
  136. 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.
  137. Any attempts to register wrappers before then will throw an exception. If using the [shim](#135-compatibility-shim), its `libWrapper` symbol will be undefined until then.
  138. ⚠ 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](#135-compatibility-shim).
  139. #### 1.3.2.2. OVERRIDE wrappers have a different call signature
  140. When using `OVERRIDE`, wrappers do not receive the next function in the wrapper chain as the first parameter. Make sure to account for this.
  141. ```javascript
  142. libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (...args) { // There is no 'wrapped' parameter in the wrapper signature
  143. console.log('Foo.prototype.bar was overridden');
  144. return;
  145. }, 'OVERRIDE');
  146. ```
  147. #### 1.3.2.3. Arrow Functions do not support `this`
  148. 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.
  149. As such, if you use arrow functions to define your wrapper, you will be unable to use the wrapper's `this`:
  150. ```javascript
  151. libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', (wrapped, ...args) => {
  152. console.log(this); // -> 'Window'
  153. }, 'MIXED' /* optional, since this is the default type */ );
  154. ```
  155. If you want access to the `this` object of the wrapped method, you must use the `function(args) { body() }` syntax:
  156. ```javascript
  157. libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (wrapped, ...args) {
  158. console.log(this); // -> 'Foo'
  159. }, 'MIXED' /* optional, since this is the default type */ );
  160. ```
  161. #### 1.3.2.4. Using `super` inside wrappers
  162. 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.
  163. 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.
  164. 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`.
  165. 1. Travel the class hierarchy automatically, using `Object.getPrototypeOf`:
  166. ```javascript
  167. Object.getPrototypeOf(ChildClass).prototype.superclass_method.apply(this, args);
  168. ```
  169. 2. Hardcode the class we wish to call:
  170. ```javascript
  171. SuperClass.prototype.superclass_method.apply(this, args);
  172. ```
  173. 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.
  174. #### 1.3.2.5. Patching Mixins
  175. 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.
  176. Instead, you should patch these methods on the classes that inherit from the mixins.
  177. For example, in the Foundry code we have the following (with irrelevant code stripped):
  178. ```javascript
  179. const CanvasDocumentMixin = Base => class extends ClientDocumentMixin(Base) {
  180. /* ... */
  181. _onCreate(data, options, userId) {
  182. /* ... */
  183. }
  184. /* ... */
  185. }
  186. /* ... */
  187. class TileDocument extends CanvasDocumentMixin(foundry.documents.BaseTile) {
  188. /* ... */
  189. }
  190. ```
  191. If we wanted to patch the method `_onCreate` which `TileDocument` inherits from `CanvasDocumentMixin(foundry.documents.BaseTile)`, we could do the following:
  192. ```javascript
  193. libWrapper.register('my-fvtt-package', 'TileDocument.prototype._onCreate', function(wrapped, ...args) {
  194. console.log("TileDocument.prototype._onCreate called");
  195. return wrapped(...args);
  196. }, 'WRAPPER');
  197. ```
  198. ### 1.3.3. LibWrapper API
  199. ⚠ Anything not documented in this section is not officially supported, and could change or break at any moment without notice.
  200. #### 1.3.3.1. Registering a wrapper
  201. To register a wrapper function, you should call the method `libWrapper.register(package_id, target, fn, type)`:
  202. ```javascript
  203. /**
  204. * Register a new wrapper.
  205. * Important: If called before the 'init' hook, this method will fail.
  206. *
  207. * 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.
  208. * 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.
  209. *
  210. * Triggers FVTT hook 'libWrapper.Register' when successful.
  211. *
  212. * Returns a unique numeric target identifier, which can be used as a replacement for 'target' in future calls to 'libWrapper.register' and 'libWrapper.unregister'.
  213. *
  214. * @param {string} package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
  215. *
  216. * @param {number|string} target The target identifier, specifying which wrapper should be registered.
  217. *
  218. * This can be either:
  219. * 1. A unique target identifier obtained from a previous 'libWrapper.register' call.
  220. * 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'.
  221. *
  222. * Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
  223. *
  224. * Since v1.8.0.0, the string path (option #2) can contain string array indexing.
  225. * For example, 'CONFIG.Actor.sheetClasses.character["dnd5e.ActorSheet5eCharacter"].cls.prototype._onLongRest' is a valid path.
  226. * It is important to note that indexing in libWrapper does not work exactly like in JavaScript:
  227. * - The index must be a single string, quoted using the ' or " characters. It does not support e.g. numbers or objects.
  228. * - 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.
  229. *
  230. * 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'.
  231. *
  232. * @param {function} fn Wrapper function. The first argument will be the next function in the chain, except for 'OVERRIDE' wrappers.
  233. * The remaining arguments will correspond to the parameters passed to the wrapped method.
  234. *
  235. * @param {string} type [Optional] The type of the wrapper. Default is 'MIXED'.
  236. *
  237. * The possible types are:
  238. *
  239. * 'WRAPPER' / libWrapper.WRAPPER:
  240. * Use if your wrapper will *always* continue the chain.
  241. * This type has priority over every other type. It should be used whenever possible as it massively reduces the likelihood of conflicts.
  242. * Note that the library will auto-detect if you use this type but do not call the original function, and automatically unregister your wrapper.
  243. *
  244. * 'MIXED' / libWrapper.MIXED:
  245. * Default type. Your wrapper will be allowed to decide whether it continue the chain or not.
  246. * These will always come after 'WRAPPER'-type wrappers. Order is not guaranteed, but conflicts will be auto-detected.
  247. *
  248. * 'OVERRIDE' / libWrapper.OVERRIDE:
  249. * Use if your wrapper will *never* continue the chain. This type has the lowest priority, and will always be called last.
  250. * If another package already has an 'OVERRIDE' wrapper registered to the same method, using this type will throw a <libWrapper.ERRORS.package> exception.
  251. * Catching this exception should allow you to fail gracefully, and for example warn the user of the conflict.
  252. * 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.
  253. *
  254. * @param {Object} options [Optional] Additional options to libWrapper.
  255. *
  256. * @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.
  257. * This parameter must be 'true' when registering non-OVERRIDE wrappers.
  258. * Default is 'false' if type=='OVERRIDE', otherwise 'true'.
  259. * First introduced in v1.3.6.0.
  260. *
  261. * @param {string} options.perf_mode [Optional] Selects the preferred performance mode for this wrapper. Default is 'AUTO'.
  262. * 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.
  263. * 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.
  264. * First introduced in v1.5.0.0.
  265. *
  266. * The possible modes are:
  267. *
  268. * 'NORMAL' / libWrapper.PERF_NORMAL:
  269. * Enables all conflict detection capabilities provided by libWrapper. Slower than 'FAST'.
  270. * Useful if wrapping a method commonly modified by other packages, to ensure most issues are detected.
  271. * In most other cases, this mode is not recommended and 'AUTO' should be used instead.
  272. *
  273. * 'FAST' / libWrapper.PERF_FAST:
  274. * Disables some conflict detection capabilities provided by libWrapper, in exchange for performance. Faster than 'NORMAL'.
  275. * Will guarantee wrapper call order and per-package prioritization, but fewer conflicts will be detectable.
  276. * This performance mode will result in comparable performance to traditional non-libWrapper wrapping methods.
  277. * Useful if wrapping a method called repeatedly in a tight loop, for example 'WallsLayer.testWall'.
  278. * In most other cases, this mode is not recommended and 'AUTO' should be used instead.
  279. *
  280. * 'AUTO' / libWrapper.PERF_AUTO:
  281. * Default performance mode. If unsure, choose this mode.
  282. * Will allow the GM to choose which performance mode to use.
  283. * Equivalent to 'FAST' when the libWrapper 'High-Performance Mode' setting is enabled by the GM, otherwise 'NORMAL'.
  284. *
  285. * @param {any[]} options.bind [Optional] An array of parameters that should be passed to 'fn'.
  286. *
  287. * This allows avoiding an extra function call, for instance:
  288. * libWrapper.register(PACKAGE_ID, "foo", function(wrapped, ...args) { return someFunction.call(this, wrapped, "foo", "bar", ...args) });
  289. * becomes
  290. * libWrapper.register(PACKAGE_ID, "foo", someFunction, "WRAPPER", {bind: ["foo", "bar"]});
  291. *
  292. * First introduced in v1.12.0.0.
  293. *
  294. * @returns {number} Unique numeric 'target' identifier which can be used in future 'libWrapper.register' and 'libWrapper.unregister' calls.
  295. * Added in v1.11.0.0.
  296. */
  297. static register(package_id, target, fn, type='MIXED', options={}) { /* ... */ }
  298. ```
  299. See the usage example above.
  300. #### 1.3.3.2. Unregistering a wrapper
  301. To unregister a wrapper function, you should call the method `libWrapper.unregister(package_id, target)`.
  302. ```javascript
  303. /**
  304. * Unregister an existing wrapper.
  305. *
  306. * Triggers FVTT hook 'libWrapper.Unregister' when successful.
  307. *
  308. * @param {string} package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
  309. *
  310. * @param {number|string} target The target identifier, specifying which wrapper should be unregistered.
  311. *
  312. * This can be either:
  313. * 1. A unique target identifier obtained from a previous 'libWrapper.register' call. This is the recommended option.
  314. * 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'.
  315. *
  316. * 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.
  317. *
  318. * Support for the unique target identifiers (option #1) was added in v1.11.0.0, with previous versions only supporting option #2.
  319. *
  320. * @param {function} fail [Optional] If true, this method will throw an exception if it fails to find the method to unwrap. Default is 'true'.
  321. */
  322. static unregister(package_id, target, fail=true) { /* ... */ }
  323. ```
  324. #### 1.3.3.3. Unregister all wrappers for a given package
  325. To clear all wrapper functions belonging to a given package, you should call the method `libWrapper.unregister_all(package_id)`.
  326. ```javascript
  327. /**
  328. * Unregister all wrappers created by a given package.
  329. *
  330. * Triggers FVTT hook 'libWrapper.UnregisterAll' when successful.
  331. *
  332. * @param {string} package_id The package identifier, i.e. the 'id' field in your module/system/world's manifest.
  333. */
  334. static unregister_all(package_id) { /* ... */ }
  335. ```
  336. #### 1.3.3.4. Ignore conflicts matching specific filters
  337. 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)`.
  338. ```javascript
  339. /**
  340. * Ignore conflicts matching specific filters when detected, instead of warning the user.
  341. *
  342. * This can be used when there are conflict warnings that are known not to cause any issues, but are unable to be resolved.
  343. * Conflicts will be ignored if they involve both 'package_id' and one of 'ignore_ids', and relate to one of 'targets'.
  344. *
  345. * 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.
  346. *
  347. * First introduced in v1.7.0.0.
  348. *
  349. * @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.
  350. *
  351. * @param {(string|string[])} ignore_ids Other package ID(s) with which conflicts should be ignored.
  352. *
  353. * @param {(string|string[])} targets Target(s) for which conflicts should be ignored, corresponding to the 'target' parameter to 'libWrapper.register'.
  354. * This method does not accept the unique target identifiers returned by 'libWrapper.register'.
  355. *
  356. * @param {Object} options [Optional] Additional options to libWrapper.
  357. *
  358. * @param {boolean} options.ignore_errors [Optional] If 'true', will also ignore confirmed conflicts (i.e. errors), rather than only potential conflicts (i.e. warnings).
  359. * Be careful when setting this to 'true', as confirmed conflicts are almost certainly something the user should be made aware of.
  360. * Defaults to 'false'.
  361. */
  362. static ignore_conflicts(package_id, ignore_ids, targets, options={}) { /* ... */ }
  363. ```
  364. #### 1.3.3.5. Library Versioning
  365. This library follows [Semantic Versioning](https://semver.org/), 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.
  366. The version string will always have format `<MAJOR>.<MINOR>.<PATCH>.<SUFFIX><META>`.
  367. The `MAJOR`, `MINOR`, `PATCH` and `SUFFIX` fields will always be integers.
  368. 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.
  369. 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.
  370. A few (non-exhaustive) examples of valid version strings and the corresponding `[MAJOR, MINOR, PATCH, SUFFIX, META]`:
  371. | `libWrapper.version` | `libWrapper.versions` |
  372. | -------------------- | ------------------------- |
  373. | `1.2.3.4` | `[1, 2, 3, 4, '']` |
  374. | `1.3.4.0rc` | `[1, 3, 4, 0, 'rc']` |
  375. | `2.1.0.2a` | `[2, 1, 0, 2, 'a']` |
  376. | `3.4.5.6dev` | `[3, 4, 5, 6, 'dev']` |
  377. The `libWrapper` object provides a few properties and methods to query or react to the library version:
  378. ```javascript
  379. // Properties
  380. /**
  381. * Get libWrapper version
  382. * @returns {string} libWrapper version in string form, i.e. "<MAJOR>.<MINOR>.<PATCH>.<SUFFIX><META>"
  383. */
  384. static get version() { /* ... */ }
  385. /**
  386. * Get libWrapper version
  387. * @returns {[number,number,number,number,string]} libWrapper version in array form, i.e. [<MAJOR>, <MINOR>, <PATCH>, <SUFFIX>, <META>]
  388. */
  389. static get versions() { /* ... */ }
  390. /**
  391. * Get the Git version identifier.
  392. * @returns {string} Git version identifier, usually 'HEAD' or the commit hash.
  393. */
  394. static get git_version() { /* ... */ };
  395. // Methods
  396. /**
  397. * Test for a minimum libWrapper version.
  398. * First introduced in v1.4.0.0.
  399. *
  400. * @param {number} major Minimum major version
  401. * @param {number} minor [Optional] Minimum minor version. Default is 0.
  402. * @param {number} patch [Optional] Minimum patch version. Default is 0.
  403. * @param {number} suffix [Optional] Minimum suffix version. Default is 0. First introduced in v1.5.2.0.
  404. * @returns {boolean} Returns true if the libWrapper version is at least the queried version, otherwise false.
  405. */
  406. static version_at_least(major, minor=0, patch=0, suffix=0) { /* ... */ }
  407. ```
  408. ##### 1.3.3.5.1. Testing for a specific libWrapper version
  409. 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.
  410. To test for libWrapper v1.4.0.0 or higher, the simplest way is to use `version_at_least`:
  411. ```javascript
  412. if(libWrapper.version_at_least?.(major, minor, patch, suffix)) {
  413. // libWrapper is at least major.minor.patch.suffix
  414. }
  415. else {
  416. // libWrapper is older than major.minor.patch.suffix
  417. }
  418. ```
  419. The arguments `minor`, `patch` and `suffix` are optional. Note the usage of `?.` to ensure this works (and is falsy) before v1.4.0.0.
  420. If you wish to detect versions below v1.4.0.0, you should use `versions` instead:
  421. ```javascript
  422. const [lwmajor, lwminor, lwpatch, lwsuffix] = libWrapper.versions;
  423. if(
  424. lwmajor > major || (lwmajor == major && (
  425. lwminor > minor || (lwminor == minor && (
  426. lwpatch > patch || (lwpatch == patch && lwsuffix >= suffix)
  427. ))
  428. ))
  429. ) {
  430. // libWrapper is at least major.minor.patch.suffix
  431. }
  432. else {
  433. // libWrapper is older than major.minor.patch.suffix
  434. }
  435. ```
  436. #### 1.3.3.6. Fallback / Polyfill detection
  437. To detect whether the `libWrapper` object contains the full library or a fallback/polyfill implementation (e.g. the [shim](#135-compatibility-shim)), you can check `libWrapper.is_fallback`.
  438. The library module will set this property to `false`, while fallback/polyfill implementations will set it to `true`.
  439. ```javascript
  440. /**
  441. * @returns {boolean} The real libWrapper module will always return false. Fallback implementations (e.g. poly-fill / shim) should return true.
  442. */
  443. static get is_fallback() { /* ... */ }
  444. ```
  445. #### 1.3.3.7. Exceptions
  446. Since v1.2.0.0, various custom exception classes are used by libWrapper, and available in the global `libWrapper` object.
  447. * `LibWrapperError`:
  448. - Base class for libWrapper exceptions.
  449. * `LibWrapperInternalError extends LibWrapperError`:
  450. - Internal LibWrapper error, usually indicating something is broken with the libWrapper library.
  451. - Public fields:
  452. - `package_id`: Package ID which triggered the error, if any.
  453. * `LibWrapperPackageError extends LibWrapperError`:
  454. - Error caused by a package external to libWrapper. These usually indicate conflicts, usage errors, or out-dated packages.
  455. - Public fields:
  456. - `package_id`: Package ID which triggered the error.
  457. * `LibWrapperAlreadyOverriddenError extends LibWrapperError`:
  458. - Thrown when a `libWrapper.register` call with `type='OVERRIDE'` fails because another `OVERRIDE` wrapper is already registered.
  459. - Public fields:
  460. - `package_id`: Package ID which failed to register the `OVERRIDE` wrapper.
  461. - `conflicting_id`: Package ID which already has a registered `OVERRIDE` wrapper.
  462. - `target`: Wrapper target (the `target` parameter to `libWrapper.register`).
  463. * `LibWrapperInvalidWrapperChainError extends LibWrapperError`:
  464. - 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.
  465. - Public fields:
  466. - `package_id`: Package ID which triggered the error, if any.
  467. 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.
  468. #### 1.3.3.8. Hooks
  469. Since v1.4.0.0, the libWrapper library triggers Hooks for various events, listed below:
  470. * `libWrapper.Ready`:
  471. - Triggered when libWrapper is ready to register wrappers. This will happen shortly before the FVTT `init` hook.
  472. - No Parameters.
  473. * `libWrapper.Register`:
  474. - Triggered when a `libWrapper.register` call completes successfully.
  475. - Parameters:
  476. - `1`: Package ID whose wrapper is being registered (the `package_id` parameter to `libWrapper.register`).
  477. - `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).
  478. - `3`: Wrapper type (the `type` parameter to `libWrapper.register`).
  479. - `4`: Options object (the `options` parameter to `libWrapper.register`).
  480. - `5`: Wrapper ID (the return value of `libWrapper.register`).
  481. * `libWrapper.Unregister`:
  482. - Triggered when a `libWrapper.unregister` call completes successfully.
  483. - Parameters:
  484. - `1`: Package ID whose wrapper is being unregistered (the `package_id` parameter to `libWrapper.unregister`).
  485. - `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).
  486. - `3`: Wrapper ID (the return value of `libWrapper.Register`).
  487. * `libWrapper.UnregisterAll`:
  488. - Triggered when a `libWrapper.unregister_all` call completes successfully.
  489. - Parameters:
  490. - `1`: Package ID whose wrappers are being unregistered (the `package_id` parameter to `libWrapper.unregister_all`).
  491. * `libWrapper.ConflictDetected`:
  492. - Triggered when a conflict is detected.
  493. - Parameters:
  494. - `1`: Package ID which triggered the conflict, or `«unknown»` if unknown.
  495. - `2`: Conflicting package ID.
  496. - `3`: Wrapper name (first `target` parameter provided by any module when registering a wrapper to the same method).
  497. - `4`: List of all unique `target` strings provided by modules when registering a wrapper to the same method.
  498. - If this hook returns `false`, the user will not be notified of this conflict.
  499. * `libWrapper.OverrideLost`:
  500. - Triggered when an `OVERRIDE` wrapper is replaced by a higher-priority wrapper.
  501. - Parameters:
  502. - `1`: Existing package ID whose wrapper is being unregistered.
  503. - `2`: New package ID whose wrapper is being registered.
  504. - `3`: Wrapper name (first `target` parameter provided by any module when registering a wrapper to the same method).
  505. - `4`: List of all unique `target` strings provided by modules when registering a wrapper to the same method.
  506. - If this hook returns `false`, this event will not be treated as a conflict.
  507. #### 1.3.3.9. Enumerations
  508. 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.
  509. For example, instead of using `'OVERRIDE'` in the `libWrapper.register` call, one could instead use `libWrapper.OVERRIDE`:
  510. ```javascript
  511. libWrapper.register('my-fvtt-package', 'Foo.prototype.bar', function (...args) {
  512. /* ... */
  513. }, libWrapper.OVERRIDE /* instead of 'OVERRIDE' */);
  514. ```
  515. A full list of the enumeration values provided by libWrapper follows:
  516. ```javascript
  517. static get WRAPPER() { /* ... */ };
  518. static get MIXED() { /* ... */ };
  519. static get OVERRIDE() { /* ... */ };
  520. static get PERF_NORMAL() { /* ... */ };
  521. static get PERF_AUTO() { /* ... */ };
  522. static get PERF_FAST() { /* ... */ };
  523. ```
  524. #### 1.3.3.10. Examples
  525. A list of packages using libWrapper, which can be used as further examples, can be found in the wiki page [Modules using libWrapper](https://github.com/ruipin/fvtt-lib-wrapper/wiki/Modules-using-libWrapper).
  526. ### 1.3.4. Using libWrapper inside a System
  527. The libWrapper library has official support for all types of packages, including systems.
  528. However, it is recommended that you read through the warnings and recommendations in [SYSTEMS.md](SYSTEMS.md) before you use it inside a system.
  529. ### 1.3.5. Compatibility Shim
  530. The [shim.js](shim/shim.js) file in this repository can be used to avoid a hard dependency on libWrapper.
  531. See the respective documentation in [shim/SHIM.md](shim/SHIM.md).
  532. ## 1.4. Support
  533. 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.
  534. ### 1.4.1. Module-specific Support
  535. 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.
  536. 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.
  537. 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".
  538. ### 1.4.2. Community Support
  539. The easiest way to find support when there are no module-specific support channels is to ask the community.
  540. The largest community-provided support channels are:
  541. - [FoundryVTT Discord](https://discord.gg/foundryvtt)'s #modules-troubleshooting channel
  542. - [FoundryVTT Reddit](https://www.reddit.com/r/FoundryVTT)
  543. ### 1.4.3. LibWrapper Support
  544. *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.*
  545. 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](https://github.com/ruipin/fvtt-lib-wrapper/issues). It is usually a good idea to search the existing issues first in case yours has already been answered before.
  546. 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.