private
Advanced tools
Comparing version 0.1.1 to 0.1.2
@@ -19,3 +19,3 @@ { | ||
], | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"homepage": "http://github.com/benjamn/private", | ||
@@ -22,0 +22,0 @@ "repository": { |
@@ -56,49 +56,65 @@ "use strict"; | ||
function makeAccessor(requireAutoForget) { | ||
var secrets = requireAutoForget ? null : create(null); | ||
function wrap(obj, value) { | ||
var old = obj[value.name]; | ||
defProp(obj, value.name, { value: value }); | ||
return old; | ||
} | ||
// Object.getOwnPropertyNames is the only way to enumerate non-enumerable | ||
// properties, so if we wrap it to ignore our secret keys, there should be | ||
// no way (except guessing) to access those properties. | ||
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; | ||
} | ||
} | ||
names.length = dst; | ||
return names; | ||
}); | ||
function defaultCreatorFn(object) { | ||
return create(null); | ||
} | ||
function makeAccessor(secretCreatorFn) { | ||
var brand = makeUniqueKey(); | ||
var passkey = create(null); | ||
secretCreatorFn = secretCreatorFn || defaultCreatorFn; | ||
function register(object) { | ||
var key = makeUniqueKey(); | ||
defProp(object, brand, { value: key }); | ||
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 accessor(object) { | ||
assertSecrets(); | ||
if (!hasOwn.call(object, brand)) | ||
register(object); | ||
var key = object[brand]; | ||
return secrets[key] || (secrets[key] = create(null)); | ||
return object[brand](passkey); | ||
} | ||
function assertSecrets() { | ||
if (!secrets) { | ||
throw new Error( | ||
"attempted to use accessor outside autoForget context" | ||
); | ||
} | ||
} | ||
accessor.forget = function(object) { | ||
assertSecrets(); | ||
var key = object[brand]; | ||
if (hasOwn.call(secrets, key)) { | ||
delete secrets[key]; | ||
} else if (key in secrets) { | ||
throw new Error( | ||
"attempted to forget object owned by another " + | ||
"autoForget context" | ||
); | ||
} | ||
if (hasOwn.call(object, brand)) | ||
object[brand](passkey, true); | ||
}; | ||
accessor.autoForget = function(callback, context) { | ||
var keptSecrets = secrets; | ||
secrets = create(keptSecrets); | ||
try { return callback.call(context || null) } | ||
finally { secrets = keptSecrets } | ||
}; | ||
return accessor; | ||
@@ -105,0 +121,0 @@ } |
@@ -13,3 +13,3 @@ var assert = require("assert"); | ||
assert.deepEqual(Object.keys(acc1(obj)), ["foo"]); | ||
assert.strictEqual(Object.getOwnPropertyNames(acc1(obj)).length, 2); | ||
assert.strictEqual(Object.getOwnPropertyNames(acc1(obj)).length, 1); | ||
assert.strictEqual(Object.getOwnPropertyNames(acc1(acc1(obj))).length, 0); | ||
@@ -32,3 +32,3 @@ acc1(obj).bar = "baz"; | ||
assert.deepEqual(acc2(obj), {}); | ||
assert.strictEqual(Object.getOwnPropertyNames(obj).length, 2); | ||
assert.strictEqual(Object.getOwnPropertyNames(obj).length, 0); | ||
assert.strictEqual(Object.keys(obj).length, 0); | ||
@@ -51,71 +51,21 @@ acc2(obj).bar = "asdf"; | ||
var context = {}; | ||
acc2.autoForget(function() { | ||
assert.strictEqual(this, context); | ||
assert.strictEqual(acc2(obj).bar, "zxcv"); | ||
function creatorFn(object) { | ||
return { self: object }; | ||
} | ||
try { | ||
acc2.forget(obj); | ||
throw new Error("threw wrong error"); | ||
} catch (err) { | ||
assert.strictEqual( | ||
err.message, | ||
"attempted to forget object owned by another autoForget context" | ||
); | ||
} | ||
var acc3 = makeAccessor(creatorFn); | ||
acc2(obj).bar = "uiop"; | ||
acc3(obj).xxx = "yyy"; | ||
assert.deepEqual(acc3(obj), { | ||
self: obj, | ||
xxx: "yyy" | ||
}); | ||
var s1 = acc2(context); | ||
s1.name = "context"; | ||
assert.deepEqual(s1, { name: "context" }); | ||
acc3.forget(obj); | ||
assert.deepEqual(acc3(obj), { | ||
self: obj | ||
}); | ||
acc2.forget(context); | ||
var s2 = acc2(context); | ||
assert.notStrictEqual(s1, s2); | ||
assert.deepEqual(s2, {}); | ||
acc2(context).xxx = "yyy"; | ||
assert.strictEqual(s2.xxx, "yyy"); | ||
var obj2 = {}; | ||
acc2.forget(obj2); // No-op. | ||
acc2.autoForget(function() { | ||
assert.strictEqual(s2, acc2(context)); | ||
assert.strictEqual(acc2(context).xxx, "yyy"); | ||
acc2.forget(obj2); // No-op. | ||
acc2(obj2).name = "obj2"; | ||
}); | ||
acc2(obj2).xxx = "zzz"; | ||
assert.strictEqual(acc2(obj2).name, void 0); | ||
assert.strictEqual(acc2(obj2).xxx, "zzz"); | ||
acc2.forget(obj2); | ||
assert.strictEqual(acc2(obj2).xxx, void 0); | ||
assert.strictEqual(acc2(context).xxx, "yyy"); | ||
}, context); | ||
assert.deepEqual(acc2(context), {}); | ||
assert.strictEqual(acc2(obj).bar, "uiop"); | ||
var strictAcc = makeAccessor(true); | ||
try { | ||
strictAcc(context); | ||
throw new Error("threw wrong error"); | ||
} catch (err) { | ||
assert.strictEqual( | ||
err.message, | ||
"attempted to use accessor outside autoForget context" | ||
); | ||
} | ||
assert.deepEqual(strictAcc.autoForget(function() { | ||
return strictAcc(context); | ||
}), {}); | ||
var green = "\033[32m"; | ||
var reset = "\033[0m"; | ||
console.log(green + "ALL PASS" + reset); |
11850
164