*/
import strapp from './strapp.js'
import localforage from 'localforage'
-localforage.configure({
- name: 'strapp',
- version: 0.1
+localforage.config({
+ name: 'strapp'
})
/** extend localforage with an assign operation */
-localforage.assignItem = (path, data) => new Promise((resolve, reject) => {
- localforage.getItem(path, data).then((fdata) => {
- localforage.setItem(path, Object.assign(fdata, data))
- .then(resolve).catch(reject)
- }).catch((err) => {
- localforage.setItem(path, data)
- .then(resolve).catch(reject)
- })
-})
-localforage.getOrSetItem = (path, defaults) => new Promise((resolve, reject) => localforage.getItem(path).then(resolve).catch((err) => localforage.setItem(path, defaults).then(resolve).catch(reject)))
+localforage.assignItem = (path, data) => localforage.getItem(path).then((fdata) => localforage.setItem(path, fdata ? Object.assign(fdata, data) : data))
+localforage.getOrSetItem = (path, defaults) => localforage.getItem(path).then((data) => data === null ? localforage.setItem(path, defaults) : Promise.resolve(data))
+const _bootTime = new Date()
-const strappRuntime = {
- run: new StrappDirectory({
- perm: 0xFFF4,
- files: {
- keyboard: new StrappDevice(),
- mouse: new StrappDevice(),
- touch: new StrappDevice(),
- client: new StrappDirectory({
- perm: 0xFF0,
- files: {
- create: new StrappFile({
- GET(){},
- POST(){} //TODO: create client action
- })
- }
- }),
- relay: new StrappDirectory({
- perm: 0x000,
- files: {
- create: new StrappFile({
- GET(){},
- POST(){} //TODO: create relay
- })
- }
- }),
- spc: new StrappDirectory({
- perm: 0xF00,
- files: {
- create: new StrappFile({
- GET(){},
- POST(){} //TODO: spc creation (probably only internal)
- })
- }
- })
- }
- })
-}
-
-const StrappFileSystem = (() => {
- /* Internal State */
- let rootUser
- const rootDir = new StrappDirectory(StrappFile.literal({
- type: 'strapp/directory',
- perm: 0xFF4,
- path: '.'
- }))
-
- /* Internal Functions */
- const _genKeyPair = () => {
- //TODO
- return {
- pubKey: '',
- privKey: ''
- }
- }
- const _request = (location, method, data) =>
- rootDir.request(location.split('/'), method, rootUser.pubKey, data)
-
- /* API Definition */
- const StrappFileSystem = {
- request: (location, method, data) =>
- new Promise((resolve, reject) => StrappFileSystem.loadWaiters.push([
- location, method, data, resolve, reject
- ])),
- get: (location) => StrappFileSystem.request(location, 'GET'),
- set: (location, data) => StrappFileSystem.request(location, 'PUT', data),
- resolveRootUser: (path) =>
- localforage.getItem(path)
- .then((data) => Promise.resolve,
- (err) => localforage.setItem(path, _genKeyPair()))
- .then((data) => Promise.resolve(rootUser = data))
- }
- StrappFileSystem.loadWaiters = []
- StrappFileSystem.bootTime = new Date()
-
- /* Init sequence */
- StrappFileSystem.resolveRootUser('acct/local')
- .then((data) => Promise.all(
- [
- ['.', {
- acct: StrappDirectory.literal()
- }],
- ['acct', {
- local: StrappFile.literal()
- }]
- ].map((entry) => localforage.getOrSetItem(entry[0],entry[1]))
- ))
- .then((loadedFiles) => {
- rootDir.loadFiles(loadedFiles[0])
- StrappFileSystem.request = _request
- StrappFileSystem.loadWaiters
- .map((w) => _request(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
- delete StrappFileSystem.loadWaiters
- })
-
- return StrappFileSystem
-})()
-
const StrappFile = (() => {
class StrappFile extends Object {
constructor(...props) {
super()
- return Object.assign(this, new.target.defaults, ...props)
+ return Object.assign(this, new.target.literal(), ...props)
}
static literal(...props) {
- return Object.assign(Object.create(null, StrappFile.defaults), ...props)
+ return Object.assign(Object.create(null), this.defaults, ...props)
}
- static parse(fileLiteral) {
+ static parse(fileLiteral, ...props) {
switch(fileLiteral.type) {
case 'application/JSON':
- return new StrappJSON(fileLiteral)
+ return new StrappJSON(fileLiteral, ...props)
case 'strapp/directory':
- return new StrappDirectory(fileLiteral)
+ return new StrappDirectory(fileLiteral, ...props)
case 'strapp/file':
default:
- return new StrappFile(fileLiteral)
+ return new StrappFile(fileLiteral, ...props)
}
}
resolveRequest(method, pubKey, data, locStack) {
let reqPerms = 0
switch(method) {
case 'OPTIONS':
- return new Promise((resolve, reject) => {
- this.resolveClientPerms.then((perms) => {
+ return this.resolveClientPerms(pubKey).then(
+ (perms) => {
let allow = ['OPTIONS']
if (perms & 0x9)
allow.push('CONNECT')
allow.push('DELETE').push('PUT').push('POST')
if (perms & 0x4)
allow.push('GET').push('HEAD')
- resolve(allow.join(', '))
- }).catch(reject)
- })
+ return Promise.resolve(allow.join(', '))
+ },
+ (err) => Promise.reject(500)
+ )
default:
return Promise.reject(405)
case 'PUT':
reqPerms = 0x9
break
}
- return new Promise((resolve, reject) => {
- this.resolveClientPerms.then((perms) => {
- if ((reqPerms & perms) === reqPerms)
- this[method](pubKey, data).then(resolve).catch(reject)
- else
- reject(401)
- }).catch(reject)
- })
+ return this.resolveClientPerms(pubKey)
+ .then((perms) => { console.log(`got ${perms} for ${reqPerms} in ${this.path}, owned by ${this.owner} with ${this.perms.toString(16)}`)
+ return Promise.resolve(perms)})
+ .then(
+ (perms) => ((reqPerms & perms) === reqPerms) ?
+ this[method](pubKey, data) : Promise.reject(401),
+ (err) => Promise.reject(500))
}
resolveClientPerms(pubKey) {
- return new Promise((resolve, reject) => {
- if (!pubKey || pubKey === '')
- resolve(this.perms >>> 12 & 0xF)
- else if (pubKey === this.owner)
- resolve((this.perms >>> 8) & 0xF)
- else
- localforage.getItem(`acct/${pubKey}`).then((account) => {
- let grpLen = account.groups ? account.groups.length : 0
- let found = false
- for (let i = 0; i < grpLen; i++) {
- if (account.groups[i] === this.group) {
- resolve((this.perms >>> 4) & 0xF)
- found = true
- break
- }
- }
- if (!found)
- resolve(this.perms & 0xF)
- }).catch(reject)
+ if (!pubKey || pubKey === '')
+ return Promise.resolve((this.perms >>> 12) & 0xF)
+ return localforage.getItem(`acct/${this.owner}`).then((fData) => {
+ if (fData && 'pubKey' in fData && fData.pubKey === pubKey)
+ return Promise.resolve((this.perms >>> 8) & 0xF)
+ return localforage.getItem(`acct/${pubKey}`).then((account) => {
+ if (account && account.groups &&
+ account.groups.some((group) => group === this.group))
+ return Promise.resolve((this.perms >>> 4) & 0xF)
+ return Promise.resolve(this.perms & 0xF)
+ })
})
}
HEAD(pubKey) {
return localforage.setItem(this.path, data)
}
POST(pubKey, data) {
- return new Promise((resolve, reject) => {
- localforage.getItem(this.path)
- .then((fData) =>
- this.setItem(this.path, fData + data)
- .then(resolve)
- .catch(reject)
- )
- .catch(reject)
- })
+ return localforage.getItem(this.path).then(
+ (fData) => localforage.setItem(this.path, fData + data))
}
DELETE(pubKey) {
return localforage.removeItem(this.path)
}
- OPTIONS(pubKey) {
- return new Promise((resolve, reject) => {
- this.resolveClientPerms(pubKey).then((perms) => {
- }).catch(reject)
- })
- }
- CONNECT(pubKey) {
- return this.GET(pubKey)
+ CONNECT(pubKey) { //TODO
+ return Promise.reject(501)
}
TRACE(opt) {
}
}
StrappFile.defaults = {
type: 'strapp/file',
- perm: 0xF00,
+ perms: 0xF00,
owner: 'local',
group: '',
- changed: StrappFileSystem.bootTime,
- created: StrappFileSystem.bootTime,
- accessed: StrappFileSystem.bootTime,
- files: undefined
+ changed: _bootTime,
+ created: _bootTime,
+ accessed: _bootTime
}
return StrappFile
})()
const _traverse_loaded = function(method, pubKey, data, locStack) {
if (locStack[0] in this.files)
return this.files[locStack[0]].resolveRequest(method, pubKey, data, locStack.slice(1))
- return Promise.reject(404)
+ console.log(`didnt find ${locStack[0]} in ${this.path}`)
+ localforage.getItem(this.path).then(console.log)
+ console.log(this.files)
+ return Promise.resolve(0)
}
const _traverse_loading = function(method, pubKey, data, locStack) {
if (this.loadWaiters)
class StrappDirectory extends StrappFile {
static literal(...props) {
- return Object.assign(Object.create(null, StrappDirectory.defaults), ...props)
- }
- loadFiles(fileData) {
- Object.keys(fileData)
- .map((key) => (this.files[key] = StrappFile.parse(fileData[key])))
- }
- resolveOwnFiles() {
- return new Promise((resolve, reject) => {
- localforage.getItem(this.path).then((fileData) => {
- this.loadFiles(fileData)
- resolve()
- }).catch(reject)
- })
+ return StrappFile.literal(this.defaults, ...props)
}
traverse(method, pubKey, data, locStack) {
+ if (this.files) {
+ this.traverse = _traverse_loaded
+ return this.traverse(method, pubKey, data, locStack)
+ }
+ this.files = {}
this.traverse = _traverse_loading
this.loadWaiters = []
- this.resolveOwnFiles().then(() => {
+ return localforage.getItem(this.path).then((fileData) => {
+ if (fileData)
+ this.loadFiles(fileData)
this.traverse = _traverse_loaded
this.loadWaiters
- .map((w) => _traverse_loaded(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
+ .map((w) => this.traverse(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
delete this.loadWaiters
+ return this.traverse(method, pubKey, data, locStack)
})
- return _traverse_loading(method, pubKey, data, locStack)
}
- request(method, pubKey, data, locStack) {
- return new Promise((resolve, reject) => {
- super.resolveRequest(method, pubKey, data, locStack)
- .then(resolve)
- .catch((err) => {
- if (err === 404)
- return this.traverse(method, pubKey, data, locStack)
- return Promise.reject(err)
- })
- })
+ loadFile (fileName, fileLiteral) {
+ this.files[fileName] = StrappFile.parse(fileLiteral, { path: this.path + '/' + fileName })
+ }
+ loadFiles (fileData) {
+ Object.keys(fileData).map((key) => this.loadFile(key, fileData[key]))
+ }
+ resolveRequest(method, pubKey, data, locStack) {
+ return super.resolveRequest(method, pubKey, data, locStack)
+ .catch((err) =>
+ err === 404 ?
+ this.traverse(method, pubKey, data, locStack) :
+ Promise.reject(err))
}
CONNECT(opts) {
//send routing message to the directory (handle the next part here)
return StrappJSON
})()
+
+
+const StrappFileSystem = (() => {
+ /* Internal State */
+ let rootKey, rootPubKey
+ const rootDir = new StrappDirectory(StrappFile.literal({
+ type: 'strapp/directory',
+ perms: 0xFF4,
+ path: '.',
+ files: {},
+ loadFile(fileName, fileLiteral) {
+ this.files[fileName] = StrappFile.parse(fileLiteral, { path: fileName })
+ }
+ }))
+ const _strappRuntime = {
+ '.': rootDir,
+ '/': rootDir,
+ run: new StrappDirectory({
+ perms: 0xFFF4,
+ files: {
+ keyboard: new StrappDevice(),
+ mouse: new StrappDevice(),
+ touch: new StrappDevice(),
+ client: new StrappDirectory({
+ perms: 0xFF0,
+ files: {
+ create: new StrappFile({
+ GET(){},
+ POST(){} //TODO: create client action
+ })
+ }
+ }),
+ relay: new StrappDirectory({
+ perms: 0x000,
+ files: {
+ create: new StrappFile({
+ GET(){},
+ POST(){} //TODO: create relay
+ })
+ }
+ }),
+ spc: new StrappDirectory({
+ perms: 0xF00,
+ files: {
+ create: new StrappFile({
+ GET(){},
+ POST(){} //TODO: spc creation (probably only internal)
+ })
+ }
+ })
+ }
+ })
+ }
+
+ /* Internal Functions */
+ const _request = (location, method, data) =>
+ rootDir.resolveRequest(method, rootPubKey, data, location.split('/'))
+
+ /* API Definition */
+ const StrappFileSystem = {
+ request: (location, method, data) =>
+ new Promise((resolve, reject) => StrappFileSystem.loadWaiters.push([
+ location, method, data, resolve, reject
+ ])),
+ get: (location) => StrappFileSystem.request(location, 'GET'),
+ set: (location, data) => StrappFileSystem.request(location, 'PUT', data)
+ }
+ StrappFileSystem.loadWaiters = []
+
+
+ /* Init sequence */
+ const _defaultFS = {
+ '.': {
+ acct: StrappDirectory.literal()
+ },
+ 'acct': {
+ local: StrappFile.literal()
+ }
+ }
+ const _algo = {
+ name: 'RSA-OAEP',
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
+ hash: { name: 'SHA-1' }
+ }
+
+ const _loadRootKeys = () =>
+ Promise.all([
+ localforage.getItem('/keys/id_rsa'),
+ localforage.getItem('/keys/id_rsa.pub')
+ ])
+ .then((files) =>
+ files[0] === null || files[1] === null ?
+ window.crypto.subtle.generateKey(_algo, true, ['encrypt', 'decrypt'])
+ .then((keyPair) => Promise.all([
+ window.crypto.subtle.exportKey('jwk', keyPair.privateKey),
+ window.crypto.subtle.exportKey('jwk', keyPair.publicKey)
+ ]))
+ .then((jwks) => Promise.all([
+ localforage.setItem('/keys/id_rsa', jwks[0]),
+ localforage.setItem('/keys/id_rsa.pub', jwks[1])
+ ])) :
+ Promise.resolve(files))
+ .then((jwks) => {
+ let hashAlg = jwks[0].alg.replace(_algo.name, '')
+ if (hashAlg.length === 0)
+ hashAlg = 'SHA-1'
+ else
+ hashAlg = 'SHA' + hashAlg
+ if (jwks[0].alg.indexOf(_algo.name) !== 0 || hashAlg != _algo.hash.name) {
+ console.log('Secure hash algorithm updated, old keys deleted')
+ return Promise.all([localforage.removeItem('/keys/id_rsa'),
+ localforage.removeItem('/keys/id_rsa.pub')
+ ]).then(_loadRootKeys)
+ }
+ rootPubKey = jwks[1].n
+ return localforage.assignItem('acct/local', { pubKey: jwks[1].n }).then(
+ () => window.crypto.subtle.importKey('jwk', jwks[0], _algo, true, ['decrypt']))
+ .then((key) => Promise.resolve(rootKey = key))
+ })
+
+ const _init = () => localforage.getItem('.')
+ .then((data) =>
+ data === null ?
+ Promise.all(
+ Object.keys(_defaultFS)
+ .map((key) => localforage.setItem(key, _defaultFS[key])))
+ .then(() => localforage.getItem('.')) :
+ Promise.resolve(data))
+ .then((rootFiles) => {
+ rootDir.loadFiles(rootFiles)
+ Object.assign(rootDir.files, _strappRuntime)
+ _loadRootKeys().then((data) => {
+ StrappFileSystem.request = _request
+ StrappFileSystem.loadWaiters
+ .map((w) => _request(w[0], w[1], w[2]).then(w[3], w[4]))
+ delete StrappFileSystem.loadWaiters
+ })
+ })
+
+ _init()
+
+ //localforage.clear().then(_init)
+
+ return StrappFileSystem
+})()
+
+
export default StrappFileSystem
-export { StrappFile, StrappPeerConnection, StrappDirectory }
+export {
+ StrappFile,
+ StrappPeerConnection,
+ StrappDirectory,
+ StrappDevice,
+ StrappJSON
+}