8101311dc5c503bac2eb7b6b0c5cd1971b8772af
[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) => StrappFileSystem.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 delete StrappFileSystem.loadWaiters
121 })
122
123 return StrappFileSystem
124 })()
125
126 const StrappFile = (() => {
127 class StrappFile extends Object {
128 constructor(...props) {
129 super()
130 return Object.assign(this, new.target.defaults, ...props)
131 }
132 static literal(...props) {
133 return Object.assign(Object.create(null, StrappFile.defaults), ...props)
134 }
135 static parse(fileLiteral) {
136 switch(fileLiteral.type) {
137 case 'application/JSON':
138 return new StrappJSON(fileLiteral)
139 case 'strapp/directory':
140 return new StrappDirectory(fileLiteral)
141 case 'strapp/file':
142 default:
143 return new StrappFile(fileLiteral)
144 }
145 }
146 resolveRequest(method, pubKey, data, locStack) {
147 if (!locStack)
148 return Promise.reject(400)
149 if (locStack.length > 0)
150 return Promise.reject(404)
151 let reqPerms = 0
152 switch(method) {
153 case 'OPTIONS':
154 return new Promise((resolve, reject) => {
155 this.resolveClientPerms.then((perms) => {
156 let allow = ['OPTIONS']
157 if (perms & 0x9)
158 allow.push('CONNECT')
159 if (perms & 0x2)
160 allow.push('DELETE').push('PUT').push('POST')
161 if (perms & 0x4)
162 allow.push('GET').push('HEAD')
163 resolve(allow.join(', '))
164 }).catch(reject)
165 })
166 default:
167 return Promise.reject(405)
168 case 'PUT':
169 case 'POST':
170 case 'DELETE':
171 reqPerms = 0x2
172 break
173 case 'GET':
174 case 'HEAD':
175 reqPerms = 0x4
176 break
177 case 'CONNECT':
178 reqPerms = 0x9
179 break
180 }
181 return new Promise((resolve, reject) => {
182 this.resolveClientPerms.then((perms) => {
183 if ((reqPerms & perms) === reqPerms)
184 this[method](pubKey, data).then(resolve).catch(reject)
185 else
186 reject(401)
187 }).catch(reject)
188 })
189 }
190 resolveClientPerms(pubKey) {
191 return new Promise((resolve, reject) => {
192 if (!pubKey || pubKey === '')
193 resolve(this.perms >>> 12 & 0xF)
194 else if (pubKey === this.owner)
195 resolve((this.perms >>> 8) & 0xF)
196 else
197 localforage.getItem(`acct/${pubKey}`).then((account) => {
198 let grpLen = account.groups ? account.groups.length : 0
199 let found = false
200 for (let i = 0; i < grpLen; i++) {
201 if (account.groups[i] === this.group) {
202 resolve((this.perms >>> 4) & 0xF)
203 found = true
204 break
205 }
206 }
207 if (!found)
208 resolve(this.perms & 0xF)
209 }).catch(reject)
210 })
211 }
212 HEAD(pubKey) {
213 return Promise.resolve()
214 }
215 GET(pubKey) {
216 return localforage.getItem(this.path)
217 }
218 PUT(pubKey, data) {
219 return localforage.setItem(this.path, data)
220 }
221 POST(pubKey, data) {
222 return new Promise((resolve, reject) => {
223 localforage.getItem(this.path)
224 .then((fData) =>
225 this.setItem(this.path, fData + data)
226 .then(resolve)
227 .catch(reject)
228 )
229 .catch(reject)
230 })
231 }
232 DELETE(pubKey) {
233 return localforage.removeItem(this.path)
234 }
235 OPTIONS(pubKey) {
236 return new Promise((resolve, reject) => {
237 this.resolveClientPerms(pubKey).then((perms) => {
238 }).catch(reject)
239 })
240 }
241 CONNECT(pubKey) {
242 return this.GET(pubKey)
243 }
244 TRACE(opt) {
245 }
246 PATCH(opt) {
247 }
248 }
249 StrappFile.defaults = {
250 type: 'strapp/file',
251 perm: 0xF00,
252 owner: 'local',
253 group: '',
254 changed: StrappFileSystem.bootTime,
255 created: StrappFileSystem.bootTime,
256 accessed: StrappFileSystem.bootTime,
257 files: undefined
258 }
259 return StrappFile
260 })()
261
262 const StrappPeerConnection = (() => {
263 class StrappPeerConnection extends StrappFile {
264 GET(opts) {
265 //get metadata (held in filesystem), with owner, usage info, etc
266 //if unauthed, send message down socket
267 }
268 PUT(opts) {
269 //create w/ sdp, register callback (or pipe), set owner
270 }
271 POST(opts) {
272 //send msg
273 }
274 routeMessage(msg) {
275 //send routing message down socket
276 //POST(opts.routemessage)
277 }
278 }
279 return StrappPeerConnection
280 })()
281
282 const StrappDirectory = (() => {
283 const _traverse_loaded = function(method, pubKey, data, locStack) {
284 if (locStack[0] in this.files)
285 return this.files[locStack[0]].resolveRequest(method, pubKey, data, locStack.slice(1))
286 return Promise.reject(404)
287 }
288 const _traverse_loading = function(method, pubKey, data, locStack) {
289 if (this.loadWaiters)
290 return new Promise((resolve, reject) => this.loadWaiters.push(
291 [method, pubKey, data, locStack, resolve, reject]
292 ))
293 return _traverse_loaded(method, pubKey, data, locStack)
294 }
295
296 class StrappDirectory extends StrappFile {
297 static literal(...props) {
298 return Object.assign(Object.create(null, StrappDirectory.defaults), ...props)
299 }
300 loadFiles(fileData) {
301 Object.keys(fileData)
302 .map((key) => (this.files[key] = StrappFile.parse(fileData[key])))
303 }
304 resolveOwnFiles() {
305 return new Promise((resolve, reject) => {
306 localforage.getItem(this.path).then((fileData) => {
307 this.loadFiles(fileData)
308 resolve()
309 }).catch(reject)
310 })
311 }
312 traverse(method, pubKey, data, locStack) {
313 this.traverse = _traverse_loading
314 this.loadWaiters = []
315 this.resolveOwnFiles().then(() => {
316 this.traverse = _traverse_loaded
317 this.loadWaiters
318 .map((w) => _traverse_loaded(w[0], w[1], w[2], w[3]).then(w[4], w[5]))
319 delete this.loadWaiters
320 })
321 return _traverse_loading(method, pubKey, data, locStack)
322 }
323 request(method, pubKey, data, locStack) {
324 return new Promise((resolve, reject) => {
325 super.resolveRequest(method, pubKey, data, locStack)
326 .then(resolve)
327 .catch((err) => {
328 if (err === 404)
329 return this.traverse(method, pubKey, data, locStack)
330 return Promise.reject(err)
331 })
332 })
333 }
334 CONNECT(opts) {
335 //send routing message to the directory (handle the next part here)
336 }
337 }
338 StrappDirectory.defaults = {
339 type: 'strapp/directory'
340 }
341 return StrappDirectory
342 })()
343
344 const StrappDevice = (() => {
345 class StrappDevice extends StrappFile {
346 }
347 return StrappDevice
348 })()
349
350 const StrappJSON = (() => {
351 class StrappJSON extends StrappFile {
352 POST(pubKey, data) {
353 return localforage.assignItem(this.path, data)
354 }
355 }
356 return StrappJSON
357 })()
358
359 export default StrappFileSystem
360 export { StrappFile, StrappPeerConnection, StrappDirectory }