* @copyright August 2017 - Ken Grimes, Jordan Lavatai
* @summmary File system implementation for a strapp node
*/
-import LocalForage from "localforage"
-
-const StrappFile = (() => {
- const localforage = LocalForage.createInstance({
- driver: [LocalForage.LOCALSTORAGE,
- LocalForage.INDEXEDDB,
- LocalForage.WEBSQL],
- name: 'strapp',
- version: 0.1,
- storeName: 'strapp'
+import strapp from './strapp.js'
+import localforage from 'localforage'
+localforage.configure({
+ name: 'strapp',
+ version: 0.1
+})
+/** 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)
})
- const authorize = (pubKey, mode, stat) => {
- let allowed
- if (pubKey === stat.owner)
- allowed = (stat.perms >>> 16) & 0xF
- else {
- let gAccess = false
- let uGroups = StrappFile.get(`acct/${pubKey}/groups`).split(' ')
- for (let i = 0; i < uGroups.length; i++) {
- if (uGroups[i] === stat.group) {
- gAccess = true
- break
+})
+localforage.getOrSetItem = (path, defaults) => new Promise((resolve, reject) => localforage.getItem(path).then(resolve).catch((err) => localforage.setItem(path, defaults).then(resolve).catch(reject)))
+
+
+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
+ })
}
- }
- if (gAccess)
- allowed = (stat.perms >>> 8) & 0xF
- else
- allowed = stat.perms & 0xF
- }
- switch(mode){
- case 'r+':
- case 'rw':
- case 'wr':
- return (allowed & 0x6) === 0x6
- case 'w':
- return (allowed & 0x2) === 0x2
- case 'r':
- return (allowed & 0x4) === 0x4
- case 'x':
- return (allowed & 0x1) === 0x1
- default:
- console.log(`Unknown access mode: ${mode}`)
- return false
+ }),
+ 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) => this.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]))
+ })
+
+ return StrappFileSystem
+})()
+
+const StrappFile = (() => {
class StrappFile extends Object {
constructor(...props) {
super()
return Object.assign(this, new.target.defaults, ...props)
}
- static PermissionDenied() {
- return new Promise((resolve, reject) => reject('Permission denied'))
- }
- static get(path) {
- return localforage.getItem(path)
+ static literal(...props) {
+ return Object.assign(Object.create(null, StrappFile.defaults), ...props)
}
- static set(path, data) {
- return localforage.setItem(path, data)
+ static parse(fileLiteral) {
+ switch(fileLiteral.type) {
+ case 'application/JSON':
+ return new StrappJSON(fileLiteral)
+ case 'strapp/directory':
+ return new StrappDirectory(fileLiteral)
+ case 'strapp/file':
+ default:
+ return new StrappFile(fileLiteral)
+ }
}
- static delete(path) {
- return localforage.removeItem(path)
+ resolveRequest(method, pubKey, data, locStack) {
+ if (!locStack)
+ return Promise.reject(400)
+ if (locStack.length > 0)
+ return Promise.reject(404)
+ let reqPerms = 0
+ switch(method) {
+ case 'OPTIONS':
+ return new Promise((resolve, reject) => {
+ this.resolveClientPerms.then((perms) => {
+ let allow = ['OPTIONS']
+ if (perms & 0x9)
+ allow.push('CONNECT')
+ if (perms & 0x2)
+ allow.push('DELETE').push('PUT').push('POST')
+ if (perms & 0x4)
+ allow.push('GET').push('HEAD')
+ resolve(allow.join(', '))
+ }).catch(reject)
+ })
+ default:
+ return Promise.reject(405)
+ case 'PUT':
+ case 'POST':
+ case 'DELETE':
+ reqPerms = 0x2
+ break
+ case 'GET':
+ case 'HEAD':
+ reqPerms = 0x4
+ break
+ case 'CONNECT':
+ 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)
+ })
}
- static routeMessage(lmkid) {
- //split lmkid by spaces
- //regex sanitize. if '/', MSG. else if ' ', resolve method
+ 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)
+ })
}
- HEAD(opt) {
- if (authorize(opt.pubKey, 'r', this.stat))
- return new Promise((resolve, reject) => resolve(''))
- else
- return StrappFile.PermissionDenied()
+ HEAD(pubKey) {
+ return Promise.resolve()
}
- GET(opt) {
- if (authorize(opt.pubKey, 'r', this.stat))
- return StrappFile.get(this.path)
- else return StrappFile.PermissionDenied()
+ GET(pubKey) {
+ return localforage.getItem(this.path)
}
- PUT(opt) {
- if (authorize(opt.pubKey, 'w', this.stat))
- return StrappFile.set(this.path, opt.data)
- else return StrappFile.PermissionDenied()
+ PUT(pubKey, data) {
+ return localforage.setItem(this.path, data)
}
- POST(opt) {
- return this.PUT(Object.assign(opt, { data: this.GET(opt) + opt.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)
+ })
}
- DELETE(opt) {
- if (authorize(opt.pubKey, 'w', this.stat))
- return StrappFile.delete(this.path)
- else return StrappFile.PermissionDenied()
+ DELETE(pubKey) {
+ return localforage.removeItem(this.path)
}
- OPTIONS(opt) {
- return this.stat
+ OPTIONS(pubKey) {
+ return new Promise((resolve, reject) => {
+ this.resolveClientPerms(pubKey).then((perms) => {
+ }).catch(reject)
+ })
}
- CONNECT(opt) { //make channel
- return this.GET(opt)
+ CONNECT(pubKey) {
+ return this.GET(pubKey)
}
TRACE(opt) {
}
}
}
StrappFile.defaults = {
- stat: {
- type: 'mime/type',
- perm: 0,
- owner: 'thisOwnerPubKey',
- group: 'groupname',
- changed: 'time',
- created: 'time',
- accessed: 'time - not saved'
- }
+ type: 'strapp/file',
+ perm: 0xF00,
+ owner: 'local',
+ group: '',
+ changed: StrappFileSystem.bootTime,
+ created: StrappFileSystem.bootTime,
+ accessed: StrappFileSystem.bootTime,
+ files: undefined
}
return StrappFile
})()
POST(opts) {
//send msg
}
- MSG(opts) {
+ routeMessage(msg) {
//send routing message down socket
//POST(opts.routemessage)
}
})()
const StrappDirectory = (() => {
+ 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)
+ }
+ const _traverse_loading = function(method, pubKey, data, locStack) {
+ if (this.loadWaiters)
+ return new Promise((resolve, reject) => this.loadWaiters.push(
+ [method, pubKey, data, locStack, resolve, reject]
+ ))
+ return _traverse_loaded(method, pubKey, data, locStack)
+ }
+
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)
+ })
+ }
+ traverse(method, pubKey, data, locStack) {
+ this.traverse = _traverse_loading
+ this.loadWaiters = []
+ this.resolveOwnFiles().then(() => {
+ this.traverse = _traverse_loaded
+ this.loadWaiters
+ .map((w) => _traverse_loaded(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
+ delete this.loadWaiters
+ })
+ 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)
+ })
+ })
+ }
CONNECT(opts) {
//send routing message to the directory (handle the next part here)
}
}
+ StrappDirectory.defaults = {
+ type: 'strapp/directory'
+ }
return StrappDirectory
})()
+const StrappDevice = (() => {
+ class StrappDevice extends StrappFile {
+ }
+ return StrappDevice
+})()
+const StrappJSON = (() => {
+ class StrappJSON extends StrappFile {
+ POST(pubKey, data) {
+ return localforage.assignItem(this.path, data)
+ }
+ }
+ return StrappJSON
+})()
-export default StrappFile
-export { StrappPeerConnection, StrappDirectory }
+export default StrappFileSystem
+export { StrappFile, StrappPeerConnection, StrappDirectory }