Click here to share this article on LinkedIn »

A very often question people ask — how to have private (or protected, or just privileged) data or methods in stamps.

Answer — same way(s) as it is done in JavaScript in general. Here are 4 ways.

TL;DR: Closure, Symbol, WeakMap, proxy object (via the Privatize stamp).

1. Closure

This is the most known and most private way to hide your data. (Read the comments!)

Let’s create a BlockchainApi stamp with a private data token and a private function request .

const rp = require('request-promise')

const {init} = require('@stamp/it') // using the init shorthand here const BlockchainApi = init(function ({config}) {

// private data

const token = config.token

// public property

this.url = config.url // private method accesses both the public and the private data

const request = ({path = '', body, method = 'GET'} = {}) => {

return rp({

url: this.url + '/' + path, // url is public

headers: {authorization: token}, // but token is private

json: true,

body,

method

})

} // public method accesses the private method

this.ping = () => request()

})

In another file:

const config = {token: '1', url: 'http://i.ua'}

const blockchainApi = BlockchainApi({config}) // create instance blockchainApi.ping().then(() => console.log('ok')) // have access

As you can see the stamp BlockchainApi has a single initializer. In it we are creating the public method ping , which has access to the private method request , which, in turn, has access to the private data token .

This means that every new method which need access to the request would have to be part of this closure. That can be less than ideal sometimes. Stamps better be tiny.

Pros:

This is the most private way to hide data. None can access it. Private methods can access some public methods ( request have access to ping ).

Cons:

Not memory efficient. It’s creating two closured functions per each object instance. To add another method (e.g. verifyBlock() ) it should be part of the same init function. Bloating single stamp is a bad idea in general. If BlockchainApi stamp is composed with another stamp then it won’t be able to access the request method.

2. Symbol

Read the comments.

const rp = require('request-promise')

const stampit = require('@stamp/it')



const requestMethod = Symbol('requestMethod')

const tokenData = Symbol('tokenData')



const BlockchainApi = stampit({

init ({config}) {

// protected data

this[tokenData] = config.token

// public property

this.url = config.url

},



methods: {

// protected method accesses both public and protected data

[requestMethod] ({path = '', body, method = 'GET'} = {}) {

return rp({

url: this.url + '/' + path,

headers: {authorization: this[tokenData]},

json: true,

body,

method

})

},



ping () {

// public method accesses the protected method

return this[requestMethod]()

}

}

})

In another file:

const config = {token: '1', url: 'http://i.ua'}

const blockchainApi = BlockchainApi({config}) // create instance



blockchainApi.ping().then(() => console.log('ok')) // have access

We are using Symbols as property keys. This means if code have access to the symbol itself then it can retrieve the “protected” data.

Pros:

This is a full proof approach to hide data. Applicable most of the times. Memory friendly, unlike closures. Protected methods can access all public methods.

Cons:

The private data and method can still be accessed and modified with some more JavaScript code (example). To add another method (e.g. verifyBlock() ) it should be part of the same file. If this BlockchainApi stamp is composed with another stamp from another file then it won’t be able to access request method.

3. WeakMap

const rp = require('request-promise')

const stampit = require('@stamp/it')



const mapOfPrivateObjects = new WeakMap()



// private method accesses both the public and private data

function requestMethod ({path = '', body, method = 'GET'} = {}) {

return rp({

url: this.url + '/' + path,

headers: {authorization: mapOfPrivateObjects.get(this).token},

json: true,

body,

method

})

}



const BlockchainApi = stampit({

init ({config}) {

// private data and method

mapOfPrivateObjects.set(this, {

token: config.token,

request: (...args) => requestMethod.apply(this, ...args)

})

// public property

this.url = config.url

},



methods: {

ping () {

// public method accesses the private method

return mapOfPrivateObjects.get(this).request()

}

}

})

In another file:

const config = {token: '1', url: 'http://i.ua'}

const blockchainApi = BlockchainApi({config}) // create instance blockchainApi.ping().then(() => console.log('ok')) // have access

We are using WeakMap as a storage of private data per each object instance. This is better than Symbols, the data is truly private now. Also, this is better than Closures — more memory effective.

Pros:

This is a private way to hide data. None can change it. Memory friendly. Private methods can access any public methods.

Cons:

To add another method (e.g. verifyBlock() ) it should be part of the same file. If this BlockchainApi stamp is composed with another stamp from another file then it won’t be able to access request method.

4. Proxy object via the Privatize stamp

If you compose the Privatize with your stamp then the objects created from your new stamp will be Wrapper Objects. Those will wrap every method (except the methods you declared as private) into wrapper methods, which will call original Object Instance methods. See picture.

Left: Object Instance pointing to its Prototype. Right: Wrapper Object calling Object Instance methods, which is pointing to its Prototype.

The returned Wrapper Object will have same set of methods as the original Object Instance, but will not have any properties.

const rp = require('request-promise')

const stampit = require('@stamp/it')

const {privatizeMethods} = require('@stamp/privatize')



const BlockchainApi = stampit({

init({config}) {

// private data

this.token = config.token

// private data too!!! But was public in previous examples

this.url = config.url

}

},

privatizeMethods('request'), // Hiding a method

{

methods: {

// private method accesses private data, or public methods

request({path = '', body, method = 'GET'} = {}) {

return rp({

url: this.url + '/' + path,

headers: {authorization: this.token},

json: true,

body,

method

})

},



ping() {

// public method accesses the private method

return this.request()

}

}

})

In another file:

const config = {token: '1', url: 'http://i.ua'}

const blockchainApi = BlockchainApi({config}) // create instance blockchainApi.ping().then(() => console.log('ok')) // have access

Not sure if Pro or Con, but all public methods can access all private methods (like the protected keyword in classic classes). So, if you have access to object instance created from the stamp — you cannot access the private data and methods. However, you can take the original stamp, add one more method to it, and grant yourself access to all private data and methods.

Pros:

Only members of the stamp(s) can access the private data and methods. All private methods can access all public methods. You can add other methods (e.g. verifyBlock() ) from other files. (If this BlockchainApi stamp is composed with another stamp from another file then every method there will be able to access the request method.)

Cons:

Not memory friendly. Creates a small wrapper function for each method. All the properties become private. You’d need to create a getter for public properties. E.g. getUrl() .

Have fun with stamps!