5b2df7b162ce600b3c712a326156e22af18280bb
[henge/kiak.git] / src / strappFileSystem.js
1 /**
2 * @file Strapp File System
3 * @author Jordan Lavatai, Ken Grimes
4 * @version 0.0.1
5 * @license AGPL-3.0
6 * @copyright August 2017 - Ken Grimes, Jordan Lavatai
7 * @summmary File system implementation for a strapp node
8 */
9 import strapp from './strapp.js'
10 import localforage from 'localforage'
11 localforage.configure({
12 name: 'strapp',
13 version: 0.1
14 })
15 /** extend localforage with an assign operation */
16 localforage.assignItem = (path, data) => new Promise((resolve, reject) => {
17 localforage.getItem(path, data).then((fdata) => {
18 localforage.setItem(path, Object.assign(fdata, data))
19 .then(resolve).catch(reject)
20 }).catch((err) => {
21 localforage.setItem(path, data)
22 .then(resolve).catch(reject)
23 })
24 })
25 localforage.getOrSetItem = (path, defaults) => new Promise((resolve, reject) => localforage.getItem(path).then(resolve).catch((err) => localforage.setItem(path, defaults).then(resolve).catch(reject)))
26
27
28 const strappRuntime = {
29 run: new StrappDirectory({
30 perm: 0xFFF4,
31 files: {
32 keyboard: new StrappDevice(),
33 mouse: new StrappDevice(),
34 touch: new StrappDevice(),
35 client: new StrappDirectory({
36 perm: 0xFF0,
37 files: {
38 create: new StrappFile({
39 GET(){},
40 POST(){} //TODO: create client action
41 })
42 }
43 }),
44 relay: new StrappDirectory({
45 perm: 0x000,
46 files: {
47 create: new StrappFile({
48 GET(){},
49 POST(){} //TODO: create relay
50 })
51 }
52 }),
53 spc: new StrappDirectory({
54 perm: 0xF00,
55 files: {
56 create: new StrappFile({
57 GET(){},
58 POST(){} //TODO: spc creation (probably only internal)
59 })
60 }
61 })
62 }
63 })
64 }
65
66 const StrappFileSystem = (() => {
67 /* Internal State */
68 let rootUser
69 const rootDir = new StrappDirectory(StrappFile.literal({
70 type: 'strapp/directory',
71 perm: 0xFF4,
72 path: '.'
73 }))
74
75 /* Internal Functions */
76 const _genKeyPair = () => {
77 //TODO
78 return {
79 pubKey: '',
80 privKey: ''
81 }
82 }
83 const _request = (location, method, data) =>
84 rootDir.request(location.split('/'), method, rootUser.pubKey, data)
85
86 /* API Definition */
87 const StrappFileSystem = {
88 request: (location, method, data) =>
89 new Promise((resolve, reject) => this.loadWaiters.push([
90 location, method, data, resolve, reject
91 ])),
92 get: (location) => StrappFileSystem.request(location, 'GET'),
93 set: (location, data) => StrappFileSystem.request(location, 'PUT', data),
94 resolveRootUser: (path) =>
95 localforage.getItem(path)
96 .then((data) => Promise.resolve,
97 (err) => localforage.setItem(path, _genKeyPair()))
98 .then((data) => Promise.resolve(rootUser = data))
99 }
100 StrappFileSystem.loadWaiters = []
101 StrappFileSystem.bootTime = new Date()
102
103 /* Init sequence */
104 StrappFileSystem.resolveRootUser('acct/local')
105 .then((data) => Promise.all(
106 [
107 ['.', {
108 acct: StrappDirectory.literal()
109 }],
110 ['acct', {
111 local: StrappFile.literal()
112 }]
113 ].map((entry) => localforage.getOrSetItem(entry[0],entry[1]))
114 ))
115 .then((loadedFiles) => {
116 rootDir.loadFiles(loadedFiles[0])
117 StrappFileSystem.request = _request
118 StrappFileSystem.loadWaiters
119 .map((w) => _request(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
120 })
121
122 return StrappFileSystem
123 })()
124
125 const StrappFile = (() => {
126 class StrappFile extends Object {
127 constructor(...props) {
128 super()
129 return Object.assign(this, new.target.defaults, ...props)
130 }
131 static literal(...props) {
132 return Object.assign(Object.create(null, StrappFile.defaults), ...props)
133 }
134 static parse(fileLiteral) {
135 switch(fileLiteral.type) {
136 case 'application/JSON':
137 return new StrappJSON(fileLiteral)
138 case 'strapp/directory':
139 return new StrappDirectory(fileLiteral)
140 case 'strapp/file':
141 default:
142 return new StrappFile(fileLiteral)
143 }
144 }
145 resolveRequest(method, pubKey, data, locStack) {
146 if (!locStack)
147 return Promise.reject(400)
148 if (locStack.length > 0)
149 return Promise.reject(404)
150 let reqPerms = 0
151 switch(method) {
152 case 'OPTIONS':
153 return new Promise((resolve, reject) => {
154 this.resolveClientPerms.then((perms) => {
155 let allow = ['OPTIONS']
156 if (perms & 0x9)
157 allow.push('CONNECT')
158 if (perms & 0x2)
159 allow.push('DELETE').push('PUT').push('POST')
160 if (perms & 0x4)
161 allow.push('GET').push('HEAD')
162 resolve(allow.join(', '))
163 }).catch(reject)
164 })
165 default:
166 return Promise.reject(405)
167 case 'PUT':
168 case 'POST':
169 case 'DELETE':
170 reqPerms = 0x2
171 break
172 case 'GET':
173 case 'HEAD':
174 reqPerms = 0x4
175 break
176 case 'CONNECT':
177 reqPerms = 0x9
178 break
179 }
180 return new Promise((resolve, reject) => {
181 this.resolveClientPerms.then((perms) => {
182 if ((reqPerms & perms) === reqPerms)
183 this[method](pubKey, data).then(resolve).catch(reject)
184 else
185 reject(401)
186 }).catch(reject)
187 })
188 }
189 resolveClientPerms(pubKey) {
190 return new Promise((resolve, reject) => {
191 if (!pubKey || pubKey === '')
192 resolve(this.perms >>> 12 & 0xF)
193 else if (pubKey === this.owner)
194 resolve((this.perms >>> 8) & 0xF)
195 else
196 localforage.getItem(`acct/${pubKey}`).then((account) => {
197 let grpLen = account.groups ? account.groups.length : 0
198 let found = false
199 for (let i = 0; i < grpLen; i++) {
200 if (account.groups[i] === this.group) {
201 resolve((this.perms >>> 4) & 0xF)
202 found = true
203 break
204 }
205 }
206 if (!found)
207 resolve(this.perms & 0xF)
208 }).catch(reject)
209 })
210 }
211 HEAD(pubKey) {
212 return Promise.resolve()
213 }
214 GET(pubKey) {
215 return localforage.getItem(this.path)
216 }
217 PUT(pubKey, data) {
218 return localforage.setItem(this.path, data)
219 }
220 POST(pubKey, data) {
221 return new Promise((resolve, reject) => {
222 localforage.getItem(this.path)
223 .then((fData) =>
224 this.setItem(this.path, fData + data)
225 .then(resolve)
226 .catch(reject)
227 )
228 .catch(reject)
229 })
230 }
231 DELETE(pubKey) {
232 return localforage.removeItem(this.path)
233 }
234 OPTIONS(pubKey) {
235 return new Promise((resolve, reject) => {
236 this.resolveClientPerms(pubKey).then((perms) => {
237 }).catch(reject)
238 })
239 }
240 CONNECT(pubKey) {
241 return this.GET(pubKey)
242 }
243 TRACE(opt) {
244 }
245 PATCH(opt) {
246 }
247 }
248 StrappFile.defaults = {
249 type: 'strapp/file',
250 perm: 0xF00,
251 owner: 'local',
252 group: '',
253 changed: StrappFileSystem.bootTime,
254 created: StrappFileSystem.bootTime,
255 accessed: StrappFileSystem.bootTime,
256 files: undefined
257 }
258 return StrappFile
259 })()
260
261 const StrappPeerConnection = (() => {
262 class StrappPeerConnection extends StrappFile {
263 GET(opts) {
264 //get metadata (held in filesystem), with owner, usage info, etc
265 //if unauthed, send message down socket
266 }
267 PUT(opts) {
268 //create w/ sdp, register callback (or pipe), set owner
269 }
270 POST(opts) {
271 //send msg
272 }
273 routeMessage(msg) {
274 //send routing message down socket
275 //POST(opts.routemessage)
276 }
277 }
278 return StrappPeerConnection
279 })()
280
281 const StrappDirectory = (() => {
282 const _traverse_loaded = function(method, pubKey, data, locStack) {
283 if (locStack[0] in this.files)
284 return this.files[locStack[0]].resolveRequest(method, pubKey, data, locStack.slice(1))
285 return Promise.reject(404)
286 }
287 const _traverse_loading = function(method, pubKey, data, locStack) {
288 if (this.loadWaiters)
289 return new Promise((resolve, reject) => this.loadWaiters.push(
290 [method, pubKey, data, locStack, resolve, reject]
291 ))
292 return _traverse_loaded(method, pubKey, data, locStack)
293 }
294
295 class StrappDirectory extends StrappFile {
296 static literal(...props) {
297 return Object.assign(Object.create(null, StrappDirectory.defaults), ...props)
298 }
299 loadFiles(fileData) {
300 Object.keys(fileData)
301 .map((key) => (this.files[key] = StrappFile.parse(fileData[key])))
302 }
303 resolveOwnFiles() {
304 return new Promise((resolve, reject) => {
305 localforage.getItem(this.path).then((fileData) => {
306 this.loadFiles(fileData)
307 resolve()
308 }).catch(reject)
309 })
310 }
311 traverse(method, pubKey, data, locStack) {
312 this.traverse = _traverse_loading
313 this.loadWaiters = []
314 this.resolveOwnFiles().then(() => {
315 this.traverse = _traverse_loaded
316 this.loadWaiters
317 .map((w) => _traverse_loaded(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
318 delete this.loadWaiters
319 })
320 return _traverse_loading(method, pubKey, data, locStack)
321 }
322 request(method, pubKey, data, locStack) {
323 return new Promise((resolve, reject) => {
324 super.resolveRequest(method, pubKey, data, locStack)
325 .then(resolve)
326 .catch((err) => {
327 if (err === 404)
328 return this.traverse(method, pubKey, data, locStack)
329 return Promise.reject(err)
330 })
331 })
332 }
333 CONNECT(opts) {
334 //send routing message to the directory (handle the next part here)
335 }
336 }
337 StrappDirectory.defaults = {
338 type: 'strapp/directory'
339 }
340 return StrappDirectory
341 })()
342
343 const StrappDevice = (() => {
344 class StrappDevice extends StrappFile {
345 }
346 return StrappDevice
347 })()
348
349 const StrappJSON = (() => {
350 class StrappJSON extends StrappFile {
351 POST(pubKey, data) {
352 return localforage.assignItem(this.path, data)
353 }
354 }
355 return StrappJSON
356 })()
357
358 export default StrappFileSystem
359 export { StrappFile, StrappPeerConnection, StrappDirectory }