private
Advanced tools
Comparing version 0.1.2 to 0.1.3
@@ -19,3 +19,3 @@ { | ||
], | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"homepage": "http://github.com/benjamn/private", | ||
@@ -22,0 +22,0 @@ "repository": { |
133
private.js
"use strict"; | ||
var defProp = Object.defineProperty || function(obj, name, desc) { | ||
// Normal property assignment is the best we can do if | ||
// Object.defineProperty is not available. | ||
obj[name] = desc.value; | ||
// Normal property assignment is the best we can do if | ||
// Object.defineProperty is not available. | ||
obj[name] = desc.value; | ||
}; | ||
@@ -15,5 +15,5 @@ | ||
function makeSafeToCall(fun) { | ||
defProp(fun, "call", { value: fun.call }); | ||
defProp(fun, "apply", { value: fun.apply }); | ||
return fun; | ||
defProp(fun, "call", { value: fun.call }); | ||
defProp(fun, "apply", { value: fun.apply }); | ||
return fun; | ||
} | ||
@@ -27,13 +27,13 @@ | ||
var create = Object.create || function(prototype, properties) { | ||
cloner.prototype = prototype || null; | ||
var obj = new cloner; | ||
cloner.prototype = prototype || null; | ||
var obj = new cloner; | ||
// The properties parameter is unused by this module, but I want this | ||
// shim to be as complete as possible. | ||
if (properties) | ||
for (var name in properties) | ||
if (hasOwn.call(properties, name)) | ||
defProp(obj, name, properties[name]); | ||
// The properties parameter is unused by this module, but I want this | ||
// shim to be as complete as possible. | ||
if (properties) | ||
for (var name in properties) | ||
if (hasOwn.call(properties, name)) | ||
defProp(obj, name, properties[name]); | ||
return obj; | ||
return obj; | ||
}; | ||
@@ -45,7 +45,7 @@ | ||
function makeUniqueKey() { | ||
// Collisions are highly unlikely, but this module is in the business | ||
// of making guarantees rather than safe bets. | ||
do var uniqueKey = strSlice.call(numToStr.call(rand(), 36), 2); | ||
while (hasOwn.call(uniqueKeys, uniqueKey)); | ||
return uniqueKeys[uniqueKey] = uniqueKey; | ||
// Collisions are highly unlikely, but this module is in the business of | ||
// making guarantees rather than safe bets. | ||
do var uniqueKey = strSlice.call(numToStr.call(rand(), 36), 2); | ||
while (hasOwn.call(uniqueKeys, uniqueKey)); | ||
return uniqueKeys[uniqueKey] = uniqueKey; | ||
} | ||
@@ -56,9 +56,9 @@ | ||
defProp(exports, "makeUniqueKey", { | ||
value: makeUniqueKey | ||
value: makeUniqueKey | ||
}); | ||
function wrap(obj, value) { | ||
var old = obj[value.name]; | ||
defProp(obj, value.name, { value: value }); | ||
return old; | ||
var old = obj[value.name]; | ||
defProp(obj, value.name, { value: value }); | ||
return old; | ||
} | ||
@@ -70,60 +70,61 @@ | ||
var realGetOPNs = wrap(Object, function getOwnPropertyNames(object) { | ||
for (var names = realGetOPNs(object), | ||
src = 0, | ||
dst = 0, | ||
len = names.length; | ||
src < len; | ||
++src) { | ||
if (!hasOwn.call(uniqueKeys, names[src])) { | ||
if (src > dst) { | ||
names[dst] = names[src]; | ||
} | ||
++dst; | ||
} | ||
for (var names = realGetOPNs(object), | ||
src = 0, | ||
dst = 0, | ||
len = names.length; | ||
src < len; | ||
++src) { | ||
if (!hasOwn.call(uniqueKeys, names[src])) { | ||
if (src > dst) { | ||
names[dst] = names[src]; | ||
} | ||
++dst; | ||
} | ||
names.length = dst; | ||
return names; | ||
} | ||
names.length = dst; | ||
return names; | ||
}); | ||
function defaultCreatorFn(object) { | ||
return create(null); | ||
return create(null); | ||
} | ||
function makeAccessor(secretCreatorFn) { | ||
var brand = makeUniqueKey(); | ||
var passkey = create(null); | ||
var brand = makeUniqueKey(); | ||
var passkey = create(null); | ||
secretCreatorFn = secretCreatorFn || defaultCreatorFn; | ||
secretCreatorFn = secretCreatorFn || defaultCreatorFn; | ||
function register(object) { | ||
var secret; // Created lazily. | ||
defProp(object, brand, { | ||
value: function(key, forget) { | ||
// Only code that has access to the passkey can retrieve | ||
// (or forget) the secret object. | ||
if (key === passkey) { | ||
return forget | ||
? secret = null | ||
: secret || (secret = secretCreatorFn(object)); | ||
} | ||
} | ||
}); | ||
} | ||
function register(object) { | ||
var secret; // Created lazily. | ||
function accessor(object) { | ||
if (!hasOwn.call(object, brand)) | ||
register(object); | ||
return object[brand](passkey); | ||
function vault(key, forget) { | ||
// Only code that has access to the passkey can retrieve (or forget) | ||
// the secret object. | ||
if (key === passkey) { | ||
return forget | ||
? secret = null | ||
: secret || (secret = secretCreatorFn(object)); | ||
} | ||
} | ||
accessor.forget = function(object) { | ||
if (hasOwn.call(object, brand)) | ||
object[brand](passkey, true); | ||
}; | ||
defProp(object, brand, { value: vault }); | ||
} | ||
return accessor; | ||
function accessor(object) { | ||
if (!hasOwn.call(object, brand)) | ||
register(object); | ||
return object[brand](passkey); | ||
} | ||
accessor.forget = function(object) { | ||
if (hasOwn.call(object, brand)) | ||
object[brand](passkey, true); | ||
}; | ||
return accessor; | ||
} | ||
defProp(exports, "makeAccessor", { | ||
value: makeAccessor | ||
value: makeAccessor | ||
}); |
@@ -20,5 +20,51 @@ private [![Build Status](https://travis-ci.org/benjamn/private.png?branch=master)](https://travis-ci.org/benjamn/private) | ||
Introduction | ||
Usage | ||
--- | ||
**Get or create a secret object associated with any (non-frozen) object:** | ||
```js | ||
var getSecret = require("private").makeAccessor(); | ||
var obj = Object.create(null); // any kind of object works | ||
getSecret(obj).totallySafeProperty = "p455w0rd"; | ||
console.log(Object.keys(obj)); // [] | ||
console.log(Object.getOwnPropertyNames(obj)); // [] | ||
console.log(getSecret(obj)); // { totallySafeProperty: "p455w0rd" } | ||
``` | ||
Now, only code that has a reference to both `getSecret` and `obj` can possibly access `.totallySafeProperty`. | ||
*Importantly, no global references to the secret object are retained by the `private` package, so as soon as `obj` gets garbage collected, the secret will be reclaimed as well. In other words, you don't have to worry about memory leaks.* | ||
**Create a unique property name that cannot be enumerated or guessed:** | ||
```js | ||
var secretKey = require("private").makeUniqueKey(); | ||
var obj = Object.create(null); // any kind of object works | ||
Object.defineProperty(obj, secretKey, { | ||
value: { totallySafeProperty: "p455w0rd" }, | ||
enumerable: false // optional; non-enumerability is the default | ||
}); | ||
Object.defineProperty(obj, "nonEnumerableProperty", { | ||
value: "anyone can guess my name", | ||
enumerable: false | ||
}); | ||
console.log(obj[secretKey].totallySafeProperty); // p455w0rd | ||
console.log(obj.nonEnumerableProperty); // "anyone can guess my name" | ||
console.log(Object.keys(obj)); // [] | ||
console.log(Object.getOwnPropertyNames(obj)); // ["nonEnumerableProperty"] | ||
for (var key in obj) { | ||
console.log(key); // never called | ||
} | ||
``` | ||
Because these keys are non-enumerable, you can't discover them using a `for`-`in` loop. Because `secretKey` is a long string of random characters, you would have a lot of trouble guessing it. And because the `private` module wraps `Object.getOwnPropertyNames` to exclude the keys it generates, you can't even use that interface to discover it. | ||
Unless you have access to the value of the `secretKey` property name, there is no way to access the value associated with it. So your only responsibility as secret-keeper is to avoid handing out the value of `secretKey` to untrusted code. | ||
Think of this style as a home-grown version of the first style. Note, however, that it requires a full implementation of ES5's `Object.defineProperty` method in order to make any safety guarantees, whereas the first example will provide safety even in environments that do not support `Object.defineProperty`. | ||
Rationale | ||
--- | ||
In JavaScript, the only data that are truly private are local variables | ||
@@ -25,0 +71,0 @@ whose values do not *leak* from the scope in which they were defined. |
@@ -21,6 +21,6 @@ var assert = require("assert"); | ||
try { | ||
acc1(42); | ||
throw new Error("threw wrong error"); | ||
acc1(42); | ||
throw new Error("threw wrong error"); | ||
} catch (err) { | ||
assert.ok(err); | ||
assert.ok(err); | ||
} | ||
@@ -51,3 +51,3 @@ | ||
function creatorFn(object) { | ||
return { self: object }; | ||
return { self: object }; | ||
} | ||
@@ -59,4 +59,4 @@ | ||
assert.deepEqual(acc3(obj), { | ||
self: obj, | ||
xxx: "yyy" | ||
self: obj, | ||
xxx: "yyy" | ||
}); | ||
@@ -66,3 +66,3 @@ | ||
assert.deepEqual(acc3(obj), { | ||
self: obj | ||
self: obj | ||
}); | ||
@@ -69,0 +69,0 @@ |
14005
247
163