Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
custom-ability
Advanced tools
make custom ability more easy. generate the ability which can be added to any class directly.
This library provides a simple way to inject abilities into classes. An "ability"(a "mixin" class) is defined as a set of methods that can be added(injected) to a class to enhance its functionality.
Sometimes we may feel that a class is too large, containing too many features or methods. In such cases, as a developer, we can extract some of these functions as separate abilities, which users can use selectively based on their needs.
Features:
$abilities
member on the prototype of the target class. This member records all injected ability names (i.e. abilityClass.name).$abilities
.coreMethod
parameter is set, the first method name in coreMethod
will also be checked in the target class.Usage:
Suppose we wanna add the RefCount ability to any class directly.
the RefCount
ability will add the following members to your class.
and you should implement the destroy
method which will be called
by release
/free
.
RefCount
(integer): the reference count.release()
/free()
: Decrements reference count for this instance.
If it is becoming less than 0, the object would be (self) destroyed.addRef()
: Increments the reference count for this instance
and returns the new reference count.Note: The same name of the methods will be replaced via the ability. These old methods will be lost. So, you must confirm whether there are the same methods in your class before you apply the new ability.
// ability.js
const makeAbility = require('custom-ability')
class RefCountable {
// the class methods if any:
static someClassMethod() {}
//define the instance methods here:
release() {
let result = --this.RefCount
if (result < 0) this.destroy()
return result
}
free() {
return this.release()
}
addRef() {
if (!isUndefined(this.RefCount))
++this.RefCount
else
this.RefCount = 1
}
}
// # We set the `addRef` method as the core methods.
// # The Core methods are the ability MUST have.
// # the first core method will be used to check the same ability whether the ability already added too.
module.exports = makeAbility(RefCountable, 'addRef')
Do not forget to add the "ability"
keyword to your package.json which means
the ability power with it.
// package.json
"keywords": [
"ability",
...
],
Do not forget to add the "ability.js"
file on your package root folder too.
now user use this ability like this:
const addRefAbility = require('ref-object/ability')
class MyClass {
destroy() {console.log('destroy')}
}
// someClassMethod would not be added to the class
addRefAbility(MyClass, exclude: '@someClassMethod')
const my = new MyClass
my.addRef() // add reference count
my.free() // dec reference count, do nothing
my.free() // now destroy, print the 'destroy' here.
More complicated example, you can see the events-ex/src/eventable.coffee.
Another type of injection is the "additional abilities" that can be injected using the methods and classMethods parameters. These additional methods are necessary when modifying existing methods of the target class to call the old/original method to make a certain ability work.
The injected methods are encapsulated in a closure. And the passed this
object inside the closure is not the original instance object, but self
, and the original method is referred to as super
.
In order to make certain ability to work, you need to modify some methods of the class which could call the old(original) method. this time we need the "additional abilities" now. eg, the event-able ability to AbstractObject. We need to send a notification event when the state of the object changes(life cycle). So the event-able of AbstractObject should be:
const eventable = require('events-ex/eventable')
const eventableOptions = require('./eventable-options')
module.exports = function(aClass, aOptions){
return eventable(aClass, eventableOptions(aOptions))
}
// eventable-options.js
module.exports = function (aOptions){
if (!aOptions) aOptions = {}
if (!aOptions.methods) aOptions.methods = {}
extend( aOptions.methods, {
// override methods: (btw: classMethods to override the class methods)
setObjectState(value, emitted = true) {
// The injected methods are encapsulated in a closure.
// The `this` object inside the closure is not the original instance object, but `self`, and the original method is referred to as `super`.
self= this.self
this.super.setObjectState.call(self, value)
if (emitted) self.emit(value, self)
}
})
...
return aOptions
// more detail on [AbstractObject/src/eventable-options.coffee](https://github.com/snowyu/abstract-object)
}
TODO: need to more explain:
The original eventable('events-ex/eventable')
is no useful for AbstractObject.
But we wanna the original eventable('events-ex/eventable')
knows the changes
and use it automatically.
const eventable = require 'events-ex/eventable'
class MyClass extends AbstractObject {}
// inherits MyClass, AbstractObject
eventable(MyClass)
you just do this on the AbstractObject:
const AbstractObject = require('./lib/abstract-object')
AbstractObject.$abilities = {
// "Eventable" is the AbilityClass name
Eventable: require('./lib/eventable-options')
}
module.exports = AbstractObject
This library provides a function customAbility that can inject the abilities of a "mixin" class onto another target class or object.
Abilities can be defined as static or instance methods on the "mixin" class.
var customAbility = require('custom-ability')
The injected abilities are provided by the abilityClass
parameter, which is expected to be a class. The function takes the following parameters:
arguments
abilityClass
(function): the class that provides the abilities to be injectedcoreMethod
(string|arrayOf string): optional must have coreMethod(s).
@
prefix means class/static method.isGetClassFunction
(boolean): the AbilityClass
is a function(aClass, aOptions)
to return the real Ability Class
if true. defaults to false.
return
the function customAbility should be modified its name. function customAbility(abilityClass: Function|object, coreMethod?: string|string[], isGetClassFunction = false): WithAbilityFn
The exported function returns another function (WithAbilityFn(targetClass, options?: {include?: string|string[], exclude?: string|string[], methods? : {[name: string]: Function}, , classMethods? : {[name: string]: Function}}): targetClass
) that takes two parameters:
This custom ability injection function has two arguments: function(class[, options])
class
: the target class to be injected the ability.options
(object): an optional options object that can contain the following properties:
include
(array|string): only these methods will be added(injected) to the class
@
prefix means class/static method.exclude
(array|string): these methods would not be added(injected) to the class
coreMethod
could not be excluded. It's always added(injected) to the class.@
prefix means class/static method.methods
(object): injected/hooked methods to the class
this.super()
to call the original method.this.self
is the original this
object.classMethods
(object): hooked class methods to the class, it's the same usage as the methods
.fix: use replace instead inject method if there is no such method on the target
const makeAbility = require('custom-ability')
class Feature {
$init() {
const Super = this.super
const that = this.self || this
if (Super) {
if (Super.apply(that, arguments) === 'ok') return
}
that._init.apply(that, arguments)
}
_init() {console.log('feature init')}
}
Feature.prototype.init = function() {this._init.apply(this, arguments)}
const addFeatureTo = makeAbility(Feature)
class My {
}
addFeatureTo(My)
expect(My.prototype.init).toStrictEqual(Feature.prototype.init)
fix(1.6.1): the injectMethods(AOP) starting with "$" was incorrectly replaced with the original method
const makeAbility = require('custom-ability')
class Feature {
// inject to the init method on target class
$init() {
const Super = this.super
const that = this.self || this
if (Super) {
if (Super.apply(that, arguments) === 'ok') return
}
that._init.apply(that, arguments)
}
_init() {console.log('feature init')}
}
Feature.prototype.init = function() {this._init.apply(this, arguments)}
const addFeatureTo = makeAbility(Feature)
class My {
init(doInitFeature = true) {
// the my init procedure
console.log('my init')
if (!doInitFeature) return 'ok'
}
}
addFeatureTo(My)
const obj = new My
obj.init()
// my init
// feature init
obj.init(false)
// my init
super
method too if the target has the same method.const createAbility = require('custom-ability')
class MyFeature {
static coreAbilityClassMethod(){};
coreAbilityMethod(){};
init(){
const Super = this.super // the original init method
const that = this.self || this // the instance
if (Super) {
Super.apply(that, arguments)
}
// do the init from MyFeature
console.log('init from MyFeature')
};
}
const addFeatureTo = createAbility(MyFeature, ['coreAbilityMethod', '@coreAbilityClassMethod']);
class MyClass {
init(hi) {
console.log('init from MyClass', hi)
}
}
// inject the static and instance methods to the MyClass.
addFeatureTo(MyClass);
const instance = new MyClass;
instance.init('hello');
@
prefix means class/static method.
farthest
parent class if possible. The static
methods of it will be inject to the current class, and mark it has been injected too.middle
parent classes has no the static methods of ability.
customAbility = require 'custom-ability'
class Test
one: ->
@one: ->
testable = customAbility Test #convert the class to testable ability
class Root
$abilities:
Test: -> # additional ability to Test
methods:
additional:->
two: ->
class Mid
inherits Mid, Root
$abilities:
Test: -> # additional ability to Test
methods:
additional:-> Mid.__super__.additional.apply(@, arguments)
three: ->
class A
inherits A, Mid
testable A # make the class A testable.
# A should have all static methods of Test
# Mid,Root should have no any methods of Test
for k, v of Test
Mid.should.not.have.ownProperty k
Root.should.not.have.ownProperty k
A.should.have.ownProperty k
v.should.be.equal A[k]
# A and Mid should have no any methods of Test
# the Root should have all methods of Test
for k, v of Test::
A::should.not.have.ownProperty k
Mid::should.not.have.ownProperty k
Root::should.have.ownProperty k
# Root should have additional methods:
Root::should.have.ownProperty 'additional'
Root::should.have.ownProperty 'two'
Mid::should.have.ownProperty 'additional'
Mid::should.have.ownProperty 'three'
super
method if the target has the same method. you can exclude it with normal name if it's not a core method.customAbility = require 'custom-ability'
class PropertyManagerAbility
constructor: ->@initialize.call @, arguments[gOptPos]
# the non-enumerable property and beginning with '$' will
# be injected to `initialize` method
defineProperty @::, '$initialize', ->
options = arguments[gOptPos]
options?={}
that = @
if @super and @self
inherited = @super
that = @self
inherited.apply(that, arguments)
that._initialize options if isFunction that._initialize
that.defineProperties(options.attributes)
that.assign(options)
module.exports = customAbility PropertyManagerAbility, 'assign'
add the replaceMethods option to custom ability function.
<broken change>
: additional abilities usage changed
Put the '$abilities'(object) property on your prototype of class if need to modify the class before apply ability.
$abilities
object key is the AbilityClass NameThe AbstractObject need to hook some methods to make the eventable ability work correctly.
AbstractObject = require('./lib/abstract-object')
AbstractObject.$abilities = {
# "Eventable" is the AbilityClass name
# the value is modified ability function.
Eventable: require('./lib/eventable-options')
}
module.exports = AbstractObject
the eventable-options.coffee file:
# eventable-options.coffee
module.exports = (aOptions)->
aOptions = {} unless aOptions
aOptions.methods = {} unless aOptions.methods
extend aOptions.methods,
# override methods:
setObjectState: (value, emitted = true)->
self= @self
@super.call(self, value)
self.emit value, self if emitted
return
...
return aOptions
# more detail on [AbstractObject/src/eventable-options.coffee](https://github.com/snowyu/abstract-object)
the AbstractObject's 'eventable' function:
eventable = require 'events-ex/eventable'
eventableOptions = require './eventable-options'
module.exports = (aClass, aOptions)->
eventable aClass, eventableOptions(aOptions)
ability = require('custom-ability/require')
class MyClass
#get the stateable ability from AbstractObject for MyClass
ability 'abstract-object', MyClass
$abilities
object key is the AbilityClass NameAbstractObject = require('./lib/abstract-object')
AbstractObject.$abilities = {
# "Eventable" is the AbilityClass name
# the value is modified ability function.
Eventable: require('./eventable')
}
module.exports = AbstractObject
the AbstractObject's 'eventable' function:
eventable = require 'events-ex/eventable'
module.exports = (aClass)->
eventable aClass, methods:
# override methods:
# we need to emit event when object state changes.
setObjectState: (value, emitted = true)->
self= @self
this.super.call(self, value)
self.emit value, self if emitted
return
...
# more detail on [AbstractObject/src/eventable](https://github.com/snowyu/abstract-object)
2.0.0-alpha.0 (2023-04-08)
FAQs
make custom ability more easy. generate the ability which can be added to any class directly.
The npm package custom-ability receives a total of 293 weekly downloads. As such, custom-ability popularity was classified as not popular.
We found that custom-ability demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.