Comparing version 2.2.0 to 2.3.0
@@ -8,4 +8,5 @@ # SBP Language Support | ||
- [sbp-js](https://github.com/okTurtles/sbp-js/) - JavaScript | ||
- [SBP.jl](https://github.com/snowteamer/SBP.jl/) - Julia | ||
## Implementing SBP In Your favorite language | ||
## Implementing SBP In Your Favorite Language | ||
@@ -17,5 +18,9 @@ To implement SBP, you just need to implement the [core SBP API](/docs/sbp-api.md). | ||
- Implementing the core SBP selectors (the `sbp` domain) | ||
- Implementing support for the `_init` domain "constructor" to add domain-specific state. Since most languages do not have the equivalent of a `this` dynamically scoped variable, how you approach implementing the domain-state can be unique and idiomatic to your langauge. | ||
- Making sure that your implementation of the core selectors has the same exact behavior as this one. | ||
- **Exception:** you can ignore all parts of the JS code that have to do with domain-specific state, as that relies on a JS-specific feature. See comment below for more details. | ||
Optional: | ||
- Implementing support for the `_init` domain "constructor" to add domain-specific state. Since most languages do not have the equivalent of a `this` dynamically scoped variable, how you approach implementing the domain-state can be unique and idiomatic to your langauge. **If implementing this feature is awkward, it is best to avoid implementing it at all to avoid unnecessarily exposing state.** Instead, you may encourage users to simply access a variable containing a map/object/dictionary for their state that is scoped in such a way that it is accessible to domain-specific selectors, and inaccessible to other functions. | ||
_Feel free to send us a PR adding your implementation to the list above!_ |
@@ -19,2 +19,3 @@ # SBP API | ||
- [`'sbp/selectors/lock'`](#sbpselectorslock) | ||
- [`'sbp/domains/lock'`](#sbpdomainslock) | ||
- [`'sbp/filters/global/add'`](#sbpfiltersglobaladd) | ||
@@ -55,3 +56,3 @@ - [`'sbp/filters/domain/add'`](#sbpfiltersdomainadd) | ||
- Function signature: `function (sels: [string])` | ||
- Function signature: `function (sels: string[])` | ||
@@ -96,3 +97,3 @@ Allows you to unregister selectors that were [marked unsafe](#sbpselectorsunsafe). | ||
- Function signature: `function (sels: [string])` | ||
- Function signature: `function (sels: string[])` | ||
@@ -105,14 +106,25 @@ Marks these selectors as overwritable via [`'sbp/selectors/overwrite'`](#sbpselectorsoverwrite). | ||
Remember to call `'sbp/selectors/lock'` after overwriting! | ||
Remember to call `'sbp/selectors/lock'` or `'sbp/domains/lock'` after overwriting! | ||
### `'sbp/selectors/lock'` | ||
- Function signature: `function (sels: [string])` | ||
- Function signature: `function (sels: string[])` | ||
Prevers these selectors from being overwritten again. | ||
Prevents these selectors from being unregistered and overwritten. | ||
Always call this after overwriting selectors unless they're designed to be left unsafe. | ||
Always call either this or `'sbp/domains/lock'` after overwriting selectors unless they're designed to be left unsafe. | ||
Remember that | ||
Once a selector is locked it cannot be unlocked. | ||
### `'sbp/domains/lock'` | ||
- Function signature: `function (domains?: string[])` | ||
If `domains` are passed in, prevents new selectors from being registered on the domains and also prevents existing selectors from being unregistered or overwritten. | ||
If no argument is passed in, locks all currently registered domains. | ||
This selector is ensures that rogue code cannot get access to domain state by registering a new selector on that domain, and therefore is preferred to `'sbp/selectors/lock'`. | ||
Once a domain is locked it cannot be unlocked. | ||
### `'sbp/filters/global/add'` | ||
@@ -119,0 +131,0 @@ |
# History | ||
#### 2.3.0 | ||
- Added `'sbp/domains/lock'`. Stronger protection than `'sbp/selectors/lock'`, prevents rogue code from accessing domain state. h/t **[@snowteamer](https://github.com/okTurtles/sbp-js/pull/4)** | ||
#### 2.2.0 | ||
@@ -4,0 +8,0 @@ |
46
index.js
@@ -5,6 +5,10 @@ // @flow | ||
type Domain = { | ||
locked: boolean; | ||
state: any; | ||
} | ||
type TypeFilter = (domain: string, selector: string, data: any) => ?boolean | ||
const selectors: {[string]: Function} = {} | ||
const domains: {[string]: Object} = {} | ||
const domains: {[string]: Domain} = {} | ||
const globalFilters: Array<TypeFilter> = [] | ||
@@ -46,4 +50,8 @@ const domainFilters: {[string]: Array<TypeFilter>} = {} | ||
for (const selector in sels) { | ||
const domain = domainFromSelector(selector) | ||
if (selectors[selector]) { | ||
const domainName = domainFromSelector(selector) | ||
// ensure each domain has a domain state associated with it | ||
const domain = domainName in domains ? domains[domainName] : (domains[domainName] = { state: {}, locked: false }) | ||
if (domain.locked) { | ||
(console.warn || console.log)(`[SBP WARN]: not registering selector on locked domain: '${selector}'`) | ||
} else if (selectors[selector]) { | ||
(console.warn || console.log)(`[SBP WARN]: not registering already registered selector: '${selector}'`) | ||
@@ -57,9 +65,5 @@ } else if (typeof sels[selector] === 'function') { | ||
registered.push(selector) | ||
// ensure each domain has a domain state associated with it | ||
if (!domains[domain]) { | ||
domains[domain] = { state: {} } | ||
} | ||
// call the special _init function immediately upon registering | ||
if (selector === `${domain}/_init`) { | ||
fn.call(domains[domain].state) | ||
if (selector === `${domainName}/_init`) { | ||
fn.call(domain.state) | ||
} | ||
@@ -70,3 +74,3 @@ } | ||
}, | ||
'sbp/selectors/unregister': function (sels: [string]) { | ||
'sbp/selectors/unregister': function (sels: string[]) { | ||
for (const selector of sels) { | ||
@@ -76,2 +80,5 @@ if (!unsafeSelectors[selector]) { | ||
} | ||
if (domains[domainFromSelector(selector)]?.locked) { | ||
throw new Error(`SBP: can't unregister selector on a locked domain: '${selector}'`) | ||
} | ||
delete selectors[selector] | ||
@@ -84,3 +91,3 @@ } | ||
}, | ||
'sbp/selectors/unsafe': function (sels: [string]) { | ||
'sbp/selectors/unsafe': function (sels: string[]) { | ||
for (const selector of sels) { | ||
@@ -93,3 +100,3 @@ if (selectors[selector]) { | ||
}, | ||
'sbp/selectors/lock': function (sels: [string]) { | ||
'sbp/selectors/lock': function (sels: string[]) { | ||
for (const selector of sels) { | ||
@@ -112,2 +119,17 @@ delete unsafeSelectors[selector] | ||
selectorFilters[selector].push(filter) | ||
}, | ||
'sbp/domains/lock': function (domainNames?: string[]) { | ||
// If no argument was given then locks every known domain. | ||
if (!domainNames) { | ||
for (const name in domains) { | ||
domains[name].locked = true | ||
} | ||
} else { | ||
for (const name of domainNames) { | ||
if (!domains[name]) { | ||
throw new Error(`SBP: cannot lock non-existent domain: ${name}`) | ||
} | ||
domains[name].locked = true | ||
} | ||
} | ||
} | ||
@@ -114,0 +136,0 @@ } |
@@ -20,3 +20,3 @@ 'use strict' | ||
'test/safe2': fn, | ||
'test/unsafe' () {} | ||
'test/unsafe' () {}, | ||
}) | ||
@@ -44,3 +44,38 @@ should(sels).have.length(3) | ||
}) | ||
it('should fail to lock a non-existent domain', () => { | ||
should.throws(() => { | ||
sbp('sbp/domains/lock', ['testDomain']) | ||
}) | ||
}) | ||
it('should lock a given domain', () => { | ||
sbp('sbp/selectors/register', { | ||
'testDomain/s1' () {} | ||
}) | ||
sbp('sbp/domains/lock', ['testDomain']) | ||
should(sbp('sbp/selectors/register', { 'testDomain/s2' () {} }).length).equal(0) | ||
}) | ||
it('should lock several domains at once', () => { | ||
sbp('sbp/selectors/register', { | ||
'domain1/test' () {}, | ||
'domain2/test' () {}, | ||
'domain3/test' () {} | ||
}) | ||
sbp('sbp/domains/lock', ['domain1', 'domain2']) | ||
should(sbp('sbp/selectors/register', { 'domain1/test2' () {} }).length).equal(0) | ||
should(sbp('sbp/selectors/register', { 'domain2/test2' () {} }).length).equal(0) | ||
// Fo now domain3 should not have been locked. | ||
should(sbp('sbp/selectors/register', { 'domain3/test2' () {} }).length).equal(1) | ||
}) | ||
it('should lock all domains at once', () => { | ||
sbp('sbp/domains/lock') | ||
// Now domain3 should also have been locked. | ||
should(sbp('sbp/selectors/register', { 'domain3/test2' () {} }).length).equal(0) | ||
}) | ||
it('should not unregister selectors on a locked domain', () => { | ||
sbp('sbp/domains/lock', ['test']) | ||
should.throws(() => { | ||
sbp('sbp/selectors/unregister', 'test/unsafe') | ||
}) | ||
}) | ||
// TODO: test filters | ||
}) |
{ | ||
"name": "@sbp/sbp", | ||
"version": "2.2.0", | ||
"version": "2.3.0", | ||
"description": "Selector-based Programming: JavaScript Edition", | ||
@@ -5,0 +5,0 @@ "main": "dist/main.cjs", |
@@ -67,3 +67,3 @@ # SBP: A Programming Paradigm for Building Secure Software and Operating Systems | ||
// Using SBP: | ||
import sbp from '~/shared/sbp.js' | ||
import sbp from '@sbp/sbp' | ||
@@ -184,2 +184,3 @@ // - call any selector registered | ||
- `'sbp/selectors/lock'` | ||
- `'sbp/domains/lock'` | ||
- `'sbp/filters/global/add'` | ||
@@ -191,2 +192,4 @@ - `'sbp/filters/domain/add'` | ||
- :book: **[SBP Core API](docs/sbp-api.md)** | ||
### SBP Features | ||
@@ -193,0 +196,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
56923
463
271