Last active
February 11, 2023 03:01
-
-
Save jamesdiacono/a542527e84ddd538582ff6c8384d7556 to your computer and use it in GitHub Desktop.
Safely translate capabilities between string and object reference representations, in JavaScript.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// capability_sealer.js | |
// James Diacono | |
// 2023-02-11 | |
// Public Domain | |
// A capability sealer is a two-way mapping between strings and opaque object | |
// references. A capability's string representation is suitable for transmission | |
// over the network, whereas its object representation is suitable for use | |
// within a single memory address space. | |
// Capabilities with matching string representations are guaranteed to have | |
// identical object references, making it trivial to test for equality. | |
// This module exports a constructor function | |
// make_capability_sealer(mint_ref, mint_string) | |
// that returns an object with two methods: | |
// seal(string) | |
// Returns an object reference unique to the capability string. | |
// unseal(ref) | |
// Returns a string unique to the capability object reference. | |
// The constructor's two parameters 'mint_ref' and 'mint_string' are functions | |
// that return new, unique capabilities. | |
// Here is how a capability sealer can be used: | |
// function mint_ref() { | |
// return Object.freeze({}); | |
// } | |
// function mint_string() { | |
// return String(Math.random()); | |
// } | |
// const {seal, unseal} = make_capability_sealer(mint_ref, mint_string); | |
// const apple = seal("apple"); | |
// const mango = seal("mango"); | |
// const peach = mint_ref(); | |
// seal("apple") === apple // true | |
// seal("mango") === apple // false | |
// unseal(apple) === "apple" // true | |
// unseal(mango) === "apple" // false | |
// unseal(peach) // e.g. "0.8220673618476588" | |
function ReverseWeakMap() { | |
// A WeakMap weakly references its keys, but there is no built-in structure that | |
// weakly references its values. To make such a structure, we are forced to | |
// manage memory directly using the WeakRef and FinalizationRegistry constructs. | |
// The keys of a ReverseWeakMap must be primitive values, and its values must be | |
// objects (or functions). In all other respects, ReverseWeakMap's interface is | |
// identical to that of WeakMap. | |
let object = Object.create(null); | |
let registry = new FinalizationRegistry(function on_garbage_collected(key) { | |
if (object[key] !== undefined && object[key].deref() === undefined) { | |
delete object[key]; | |
} | |
}); | |
return Object.freeze({ | |
delete(key) { | |
delete object[key]; | |
}, | |
get(key) { | |
if (object[key] !== undefined) { | |
return object[key].deref(); | |
} | |
}, | |
has(key) { | |
return ( | |
object[key] !== undefined | |
&& object[key].deref() !== undefined | |
); | |
}, | |
set(key, value) { | |
object[key] = new WeakRef(value); | |
registry.register(value, key); | |
} | |
}); | |
} | |
function make_capability_sealer(mint_ref, mint_string) { | |
// To maintain an efficient two-way mapping, we index by both object reference | |
// and string. WeakMap and ReverseWeakMap are used to ensure that objects or | |
// strings no longer in use can be garbage collected. | |
let strings = new WeakMap(); | |
let refs = new ReverseWeakMap(); | |
return Object.freeze({ | |
seal(string) { | |
let ref = refs.get(string); | |
if (ref === undefined && mint_ref !== undefined) { | |
ref = mint_ref(); | |
strings.set(ref, string); | |
refs.set(string, ref); | |
} | |
return ref; | |
}, | |
unseal(ref) { | |
let string = strings.get(ref); | |
if (string === undefined && mint_string !== undefined) { | |
string = mint_string(); | |
strings.set(ref, string); | |
refs.set(string, ref); | |
} | |
return string; | |
} | |
}); | |
} | |
export default Object.freeze(make_capability_sealer); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment