2 * @file Strapp File System
3 * @author Jordan Lavatai, Ken Grimes
6 * @copyright August 2017 - Ken Grimes, Jordan Lavatai
7 * @summmary File system implementation for a strapp node
9 import strapp
from './strapp.js'
10 import localforage
from 'localforage'
14 /** extend localforage with an assign operation */
15 localforage
.assignItem
= (path
, data
) => localforage
.getItem(path
).then((fdata
) => localforage
.setItem(path
, fdata
? Object
.assign(fdata
, data
) : data
))
16 localforage
.getOrSetItem
= (path
, defaults
) => localforage
.getItem(path
).then((data
) => data
=== null ? localforage
.setItem(path
, defaults
) : Promise
.resolve(data
))
18 const _bootTime
= new Date()
20 const StrappFile
= (() => {
21 class StrappFile
extends Object
{
22 constructor(...props
) {
24 return Object
.assign(this, new.target
.literal(), ...props
)
26 static literal(...props
) {
27 return Object
.assign(Object
.create(null), this.defaults
, ...props
)
29 static parse(fileLiteral
, ...props
) {
30 switch(fileLiteral
.type
) {
31 case 'application/JSON':
32 return new StrappJSON(fileLiteral
, ...props
)
33 case 'strapp/directory':
34 return new StrappDirectory(fileLiteral
, ...props
)
37 return new StrappFile(fileLiteral
, ...props
)
40 resolveRequest(method
, pubKey
, data
, locStack
) {
42 return Promise
.reject(400)
43 if (locStack
.length
> 0)
44 return Promise
.reject(404)
48 return this.resolveClientPerms(pubKey
).then(
50 let allow
= ['OPTIONS']
54 allow
.push('DELETE').push('PUT').push('POST')
56 allow
.push('GET').push('HEAD')
57 return Promise
.resolve(allow
.join(', '))
59 (err
) => Promise
.reject(500)
62 return Promise
.reject(405)
76 return this.resolveClientPerms(pubKey
)
77 .then((perms
) => { console
.log(`got ${perms} for ${reqPerms} in ${this.path}, owned by ${this.owner} with ${this.perms.toString(16)}`)
78 return Promise
.resolve(perms
)})
80 (perms
) => ((reqPerms
& perms
) === reqPerms
) ?
81 this[method
](pubKey
, data
) : Promise
.reject(401),
82 (err
) => Promise
.reject(500))
84 resolveClientPerms(pubKey
) {
85 if (!pubKey
|| pubKey
=== '')
86 return Promise
.resolve((this.perms
>>> 12) & 0xF)
87 return localforage
.getItem(`acct/${this.owner}`).then((fData
) => {
88 if (fData
&& 'pubKey' in fData
&& fData
.pubKey
=== pubKey
)
89 return Promise
.resolve((this.perms
>>> 8) & 0xF)
90 return localforage
.getItem(`acct/${pubKey}`).then((account
) => {
91 if (account
&& account
.groups
&&
92 account
.groups
.some((group
) => group
=== this.group
))
93 return Promise
.resolve((this.perms
>>> 4) & 0xF)
94 return Promise
.resolve(this.perms
& 0xF)
99 return Promise
.resolve()
102 return localforage
.getItem(this.path
)
105 return localforage
.setItem(this.path
, data
)
108 return localforage
.getItem(this.path
).then(
109 (fData
) => localforage
.setItem(this.path
, fData
+ data
))
112 return localforage
.removeItem(this.path
)
114 CONNECT(pubKey
) { //TODO
115 return Promise
.reject(501)
122 StrappFile
.defaults
= {
134 const StrappPeerConnection
= (() => {
135 class StrappPeerConnection
extends StrappFile
{
137 //get metadata (held in filesystem), with owner, usage info, etc
138 //if unauthed, send message down socket
141 //create w/ sdp, register callback (or pipe), set owner
147 //send routing message down socket
148 //POST(opts.routemessage)
151 return StrappPeerConnection
154 const StrappDirectory
= (() => {
155 const _traverse_loaded = function(method
, pubKey
, data
, locStack
) {
156 if (locStack
[0] in this.files
)
157 return this.files
[locStack
[0]].resolveRequest(method
, pubKey
, data
, locStack
.slice(1))
158 console
.log(`didnt find ${locStack[0]} in ${this.path}`)
159 localforage
.getItem(this.path
).then(console
.log
)
160 console
.log(this.files
)
161 return Promise
.resolve(0)
163 const _traverse_loading = function(method
, pubKey
, data
, locStack
) {
164 if (this.loadWaiters
)
165 return new Promise((resolve
, reject
) => this.loadWaiters
.push(
166 [method
, pubKey
, data
, locStack
, resolve
, reject
]
168 return _traverse_loaded(method
, pubKey
, data
, locStack
)
171 class StrappDirectory
extends StrappFile
{
172 static literal(...props
) {
173 return StrappFile
.literal(this.defaults
, ...props
)
175 traverse(method
, pubKey
, data
, locStack
) {
177 this.traverse
= _traverse_loaded
178 return this.traverse(method
, pubKey
, data
, locStack
)
181 this.traverse
= _traverse_loading
182 this.loadWaiters
= []
183 return localforage
.getItem(this.path
).then((fileData
) => {
185 this.loadFiles(fileData
)
186 this.traverse
= _traverse_loaded
188 .map((w
) => this.traverse(w
[0], w
[1], w
[2], w
[3]).then(w
[4], w
[5]))
189 delete this.loadWaiters
190 return this.traverse(method
, pubKey
, data
, locStack
)
193 loadFile (fileName
, fileLiteral
) {
194 this.files
[fileName
] = StrappFile
.parse(fileLiteral
, { path
: this.path
+ '/' + fileName
})
196 loadFiles (fileData
) {
197 Object
.keys(fileData
).map((key
) => this.loadFile(key
, fileData
[key
]))
199 resolveRequest(method
, pubKey
, data
, locStack
) {
200 return super.resolveRequest(method
, pubKey
, data
, locStack
)
203 this.traverse(method
, pubKey
, data
, locStack
) :
207 //send routing message to the directory (handle the next part here)
210 StrappDirectory
.defaults
= {
211 type
: 'strapp/directory'
213 return StrappDirectory
216 const StrappDevice
= (() => {
217 class StrappDevice
extends StrappFile
{
222 const StrappJSON
= (() => {
223 class StrappJSON
extends StrappFile
{
225 return localforage
.assignItem(this.path
, data
)
233 const StrappFileSystem
= (() => {
235 let rootKey
, rootPubKey
236 const rootDir
= new StrappDirectory(StrappFile
.literal({
237 type
: 'strapp/directory',
241 loadFile(fileName
, fileLiteral
) {
242 this.files
[fileName
] = StrappFile
.parse(fileLiteral
, { path
: fileName
})
245 const _strappRuntime
= {
248 run
: new StrappDirectory({
251 keyboard
: new StrappDevice(),
252 mouse
: new StrappDevice(),
253 touch
: new StrappDevice(),
254 client
: new StrappDirectory({
257 create
: new StrappFile({
259 POST(){} //TODO: create client action
263 relay
: new StrappDirectory({
266 create
: new StrappFile({
268 POST(){} //TODO: create relay
272 spc
: new StrappDirectory({
275 create
: new StrappFile({
277 POST(){} //TODO: spc creation (probably only internal)
285 /* Internal Functions */
286 const _request
= (location
, method
, data
) =>
287 rootDir
.resolveRequest(method
, rootPubKey
, data
, location
.split('/'))
290 const StrappFileSystem
= {
291 request
: (location
, method
, data
) =>
292 new Promise((resolve
, reject
) => StrappFileSystem
.loadWaiters
.push([
293 location
, method
, data
, resolve
, reject
295 get: (location
) => StrappFileSystem
.request(location
, 'GET'),
296 set: (location
, data
) => StrappFileSystem
.request(location
, 'PUT', data
)
298 StrappFileSystem
.loadWaiters
= []
304 acct
: StrappDirectory
.literal()
307 local
: StrappFile
.literal()
313 publicExponent
: new Uint8Array([0x01, 0x00, 0x01]),
314 hash
: { name
: 'SHA-1' }
317 const _loadRootKeys
= () =>
319 localforage
.getItem('/keys/id_rsa'),
320 localforage
.getItem('/keys/id_rsa.pub')
323 files
[0] === null || files
[1] === null ?
324 window
.crypto
.subtle
.generateKey(_algo
, true, ['encrypt', 'decrypt'])
325 .then((keyPair
) => Promise
.all([
326 window
.crypto
.subtle
.exportKey('jwk', keyPair
.privateKey
),
327 window
.crypto
.subtle
.exportKey('jwk', keyPair
.publicKey
)
329 .then((jwks
) => Promise
.all([
330 localforage
.setItem('/keys/id_rsa', jwks
[0]),
331 localforage
.setItem('/keys/id_rsa.pub', jwks
[1])
333 Promise
.resolve(files
))
335 let hashAlg
= jwks
[0].alg
.replace(_algo
.name
, '')
336 if (hashAlg
.length
=== 0)
339 hashAlg
= 'SHA' + hashAlg
340 if (jwks
[0].alg
.indexOf(_algo
.name
) !== 0 || hashAlg
!= _algo
.hash
.name
) {
341 console
.log('Secure hash algorithm updated, old keys deleted')
342 return Promise
.all([localforage
.removeItem('/keys/id_rsa'),
343 localforage
.removeItem('/keys/id_rsa.pub')
344 ]).then(_loadRootKeys
)
346 rootPubKey
= jwks
[1].n
347 return localforage
.assignItem('acct/local', { pubKey
: jwks
[1].n
}).then(
348 () => window
.crypto
.subtle
.importKey('jwk', jwks
[0], _algo
, true, ['decrypt']))
349 .then((key
) => Promise
.resolve(rootKey
= key
))
352 const _init
= () => localforage
.getItem('.')
356 Object
.keys(_defaultFS
)
357 .map((key
) => localforage
.setItem(key
, _defaultFS
[key
])))
358 .then(() => localforage
.getItem('.')) :
359 Promise
.resolve(data
))
360 .then((rootFiles
) => {
361 rootDir
.loadFiles(rootFiles
)
362 Object
.assign(rootDir
.files
, _strappRuntime
)
363 _loadRootKeys().then((data
) => {
364 StrappFileSystem
.request
= _request
365 StrappFileSystem
.loadWaiters
366 .map((w
) => _request(w
[0], w
[1], w
[2]).then(w
[3], w
[4]))
367 delete StrappFileSystem
.loadWaiters
373 //localforage.clear().then(_init)
375 return StrappFileSystem
379 export default StrappFileSystem
382 StrappPeerConnection
,