braidfs 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +91 -29
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -56,6 +56,9 @@ braid_text.db_folder = config.braid_text_db
|
|
|
56
56
|
require('fs').mkdirSync(config.proxy_base, { recursive: true })
|
|
57
57
|
require('fs').mkdirSync(config.proxy_base_last_versions, { recursive: true })
|
|
58
58
|
|
|
59
|
+
let host_to_protocol = {}
|
|
60
|
+
let path_to_func = {}
|
|
61
|
+
|
|
59
62
|
console.log({ sync_urls: config.sync_urls, sync_index_urls: config.sync_index_urls })
|
|
60
63
|
for (let url of config.sync_urls) proxy_url(url)
|
|
61
64
|
config.sync_index_urls.forEach(async url => {
|
|
@@ -71,6 +74,27 @@ braid_text.list().then(x => {
|
|
|
71
74
|
for (let xx of x) proxy_url(xx)
|
|
72
75
|
})
|
|
73
76
|
|
|
77
|
+
require('chokidar').watch(config.proxy_base).
|
|
78
|
+
on('change', (path) => {
|
|
79
|
+
path = require('path').relative(config.proxy_base, path)
|
|
80
|
+
console.log(`path changed: ${path}`)
|
|
81
|
+
|
|
82
|
+
path = normalize_url(path)
|
|
83
|
+
// console.log(`normalized path: ${path}`)
|
|
84
|
+
|
|
85
|
+
path_to_func[path]()
|
|
86
|
+
}).
|
|
87
|
+
on('add', async (path) => {
|
|
88
|
+
path = require('path').relative(config.proxy_base, path)
|
|
89
|
+
console.log(`path added: ${path}`)
|
|
90
|
+
|
|
91
|
+
let url = null
|
|
92
|
+
if (path.startsWith('localhost/')) url = path.replace(/^localhost\//, '')
|
|
93
|
+
else url = host_to_protocol[path.split('/')[0]] + '//' + path
|
|
94
|
+
|
|
95
|
+
proxy_url(url)
|
|
96
|
+
})
|
|
97
|
+
|
|
74
98
|
const server = http.createServer(async (req, res) => {
|
|
75
99
|
console.log(`${req.method} ${req.url}`);
|
|
76
100
|
|
|
@@ -113,7 +137,15 @@ const server = http.createServer(async (req, res) => {
|
|
|
113
137
|
// we don't want to let remote people access external links for now
|
|
114
138
|
if (config.allow_remote_access && is_external_link) only_allow_local_host()
|
|
115
139
|
|
|
116
|
-
proxy_url(url)
|
|
140
|
+
let p = await proxy_url(url)
|
|
141
|
+
|
|
142
|
+
res.setHeader('Editable', !p.file_read_only)
|
|
143
|
+
if (req.method == "PUT" || req.method == "POST" || req.method == "PATCH") {
|
|
144
|
+
if (p.file_read_only) {
|
|
145
|
+
res.statusCode = 403 // Forbidden status code
|
|
146
|
+
return res.end('access denied')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
117
149
|
|
|
118
150
|
// Now serve the collaborative text!
|
|
119
151
|
braid_text.serve(req, res, { key: normalize_url(url) })
|
|
@@ -124,12 +156,6 @@ server.listen(config.port, () => {
|
|
|
124
156
|
if (!config.allow_remote_access) console.log('!! only accessible from localhost !!');
|
|
125
157
|
});
|
|
126
158
|
|
|
127
|
-
////////////////////////////////
|
|
128
|
-
|
|
129
|
-
function normalize_url(url) {
|
|
130
|
-
return url.replace(/(\/index|\/)+$/, '')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
159
|
async function proxy_url(url) {
|
|
134
160
|
let chain = proxy_url.chain || (proxy_url.chain = Promise.resolve())
|
|
135
161
|
|
|
@@ -166,8 +192,9 @@ async function proxy_url(url) {
|
|
|
166
192
|
url = normalized_url
|
|
167
193
|
|
|
168
194
|
if (!proxy_url.cache) proxy_url.cache = {}
|
|
169
|
-
if (proxy_url.cache[url]) return
|
|
170
|
-
|
|
195
|
+
if (proxy_url.cache[url]) return proxy_url.cache[url]
|
|
196
|
+
let self = {}
|
|
197
|
+
proxy_url.cache[url] = self
|
|
171
198
|
|
|
172
199
|
console.log(`proxy_url: ${url}`)
|
|
173
200
|
|
|
@@ -175,6 +202,11 @@ async function proxy_url(url) {
|
|
|
175
202
|
let path = is_external_link ? url.replace(/^https?:\/\//, '') : `localhost/${url}`
|
|
176
203
|
let fullpath = require("path").join(config.proxy_base, path)
|
|
177
204
|
|
|
205
|
+
if (is_external_link) {
|
|
206
|
+
let u = new URL(url)
|
|
207
|
+
host_to_protocol[u.host] = u.protocol
|
|
208
|
+
}
|
|
209
|
+
|
|
178
210
|
// if we're accessing /blah/index, it will be normalized to /blah,
|
|
179
211
|
// but we still want to create a directory out of blah in this case
|
|
180
212
|
if (wasnt_normal && !(await is_dir(fullpath))) await ensure_path(fullpath)
|
|
@@ -191,6 +223,7 @@ async function proxy_url(url) {
|
|
|
191
223
|
var char_counter = -1
|
|
192
224
|
let file_last_version = null
|
|
193
225
|
let file_last_text = null
|
|
226
|
+
self.file_read_only = null
|
|
194
227
|
let file_needs_reading = true
|
|
195
228
|
let file_needs_writing = null
|
|
196
229
|
let file_loop_pump_lock = 0
|
|
@@ -229,7 +262,6 @@ async function proxy_url(url) {
|
|
|
229
262
|
file_last_text = (await braid_text.get(url, { version: file_last_version })).body
|
|
230
263
|
file_needs_writing = !v_eq(file_last_version, (await braid_text.get(url, {})).version)
|
|
231
264
|
} catch (e) {
|
|
232
|
-
file_last_version = []
|
|
233
265
|
file_last_text = ''
|
|
234
266
|
file_needs_writing = true
|
|
235
267
|
}
|
|
@@ -239,6 +271,8 @@ async function proxy_url(url) {
|
|
|
239
271
|
if (file_needs_reading) {
|
|
240
272
|
file_needs_reading = false
|
|
241
273
|
|
|
274
|
+
if (self.file_read_only === null) try { self.file_read_only = await is_read_only(await get_fullpath()) } catch (e) { }
|
|
275
|
+
|
|
242
276
|
let text = ''
|
|
243
277
|
try { text = await require('fs').promises.readFile(await get_fullpath(), { encoding: 'utf8' }) } catch (e) { }
|
|
244
278
|
|
|
@@ -267,11 +301,15 @@ async function proxy_url(url) {
|
|
|
267
301
|
|
|
268
302
|
console.log(`writing file ${await get_fullpath()}`)
|
|
269
303
|
|
|
304
|
+
try { if (await is_read_only(await get_fullpath())) await set_read_only(await get_fullpath(), false) } catch (e) { }
|
|
305
|
+
|
|
270
306
|
file_last_version = version
|
|
271
307
|
file_last_text = body
|
|
272
308
|
await require('fs').promises.writeFile(await get_fullpath(), file_last_text)
|
|
273
309
|
await require('fs').promises.writeFile(require('path').join(config.proxy_base_last_versions, braid_text.encode_filename(url)), JSON.stringify(file_last_version))
|
|
274
310
|
}
|
|
311
|
+
|
|
312
|
+
if (await is_read_only(await get_fullpath()) !== self.file_read_only) await set_read_only(await get_fullpath(), self.file_read_only)
|
|
275
313
|
}
|
|
276
314
|
}
|
|
277
315
|
file_loop_pump_lock--
|
|
@@ -280,7 +318,8 @@ async function proxy_url(url) {
|
|
|
280
318
|
if (is_external_link) braid_fetch_wrapper(url, {
|
|
281
319
|
headers: {
|
|
282
320
|
"Merge-Type": "dt",
|
|
283
|
-
Accept: 'text/plain'
|
|
321
|
+
Accept: 'text/plain',
|
|
322
|
+
...config?.domains?.[(new URL(url)).hostname]?.auth_headers,
|
|
284
323
|
},
|
|
285
324
|
subscribe: true,
|
|
286
325
|
retry: true,
|
|
@@ -289,6 +328,9 @@ async function proxy_url(url) {
|
|
|
289
328
|
if (cur.version.length) return cur.version
|
|
290
329
|
},
|
|
291
330
|
peer
|
|
331
|
+
}, (res) => {
|
|
332
|
+
self.file_read_only = res.headers.get('editable') === 'false'
|
|
333
|
+
signal_file_needs_writing()
|
|
292
334
|
}).then(x => {
|
|
293
335
|
x.subscribe(async update => {
|
|
294
336
|
// console.log(`update: ${JSON.stringify(update, null, 4)}`)
|
|
@@ -300,21 +342,7 @@ async function proxy_url(url) {
|
|
|
300
342
|
})
|
|
301
343
|
})
|
|
302
344
|
|
|
303
|
-
|
|
304
|
-
proxy_url.path_to_func[path] = signal_file_needs_reading
|
|
305
|
-
|
|
306
|
-
if (!proxy_url.chokidar) {
|
|
307
|
-
proxy_url.chokidar = true
|
|
308
|
-
require('chokidar').watch(config.proxy_base).on('change', (path) => {
|
|
309
|
-
path = require('path').relative(config.proxy_base, path)
|
|
310
|
-
console.log(`path changed: ${path}`)
|
|
311
|
-
|
|
312
|
-
path = normalize_url(path)
|
|
313
|
-
// console.log(`normalized path: ${path}`)
|
|
314
|
-
|
|
315
|
-
proxy_url.path_to_func[path]()
|
|
316
|
-
});
|
|
317
|
-
}
|
|
345
|
+
path_to_func[path] = signal_file_needs_reading
|
|
318
346
|
|
|
319
347
|
// try a HEAD without subscribe to get the version
|
|
320
348
|
let parents = null
|
|
@@ -322,12 +350,17 @@ async function proxy_url(url) {
|
|
|
322
350
|
try {
|
|
323
351
|
let head_res = await braid_fetch_wrapper(url, {
|
|
324
352
|
method: 'HEAD',
|
|
325
|
-
headers: {
|
|
353
|
+
headers: {
|
|
354
|
+
Accept: 'text/plain',
|
|
355
|
+
...config?.domains?.[(new URL(url)).hostname]?.auth_headers,
|
|
356
|
+
},
|
|
326
357
|
retry: true,
|
|
327
358
|
})
|
|
328
359
|
parents = head_res.headers.get('version') ?
|
|
329
360
|
JSON.parse(`[${head_res.headers.get('version')}]`) :
|
|
330
361
|
null
|
|
362
|
+
self.file_read_only = head_res.headers.get('editable') === 'false'
|
|
363
|
+
signal_file_needs_writing()
|
|
331
364
|
} catch (e) {
|
|
332
365
|
console.log('HEAD failed: ', e)
|
|
333
366
|
}
|
|
@@ -348,6 +381,14 @@ async function proxy_url(url) {
|
|
|
348
381
|
send_out({ version, parents, body, patches, peer })
|
|
349
382
|
},
|
|
350
383
|
})
|
|
384
|
+
|
|
385
|
+
return self
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
////////////////////////////////
|
|
389
|
+
|
|
390
|
+
function normalize_url(url) {
|
|
391
|
+
return url.replace(/(\/index|\/)+$/, '')
|
|
351
392
|
}
|
|
352
393
|
|
|
353
394
|
async function is_dir(p) {
|
|
@@ -431,7 +472,7 @@ function count_code_points(str) {
|
|
|
431
472
|
return code_points
|
|
432
473
|
}
|
|
433
474
|
|
|
434
|
-
async function braid_fetch_wrapper(url, params) {
|
|
475
|
+
async function braid_fetch_wrapper(url, params, connection_cb) {
|
|
435
476
|
if (!params.retry) throw "wtf"
|
|
436
477
|
var waitTime = 10
|
|
437
478
|
if (params.subscribe) {
|
|
@@ -441,6 +482,7 @@ async function braid_fetch_wrapper(url, params) {
|
|
|
441
482
|
if (params.signal?.aborted) return
|
|
442
483
|
try {
|
|
443
484
|
var c = await braid_fetch(url, { ...params, parents: await params.parents?.() })
|
|
485
|
+
connection_cb(c)
|
|
444
486
|
c.subscribe((...args) => subscribe_handler?.(...args), on_error)
|
|
445
487
|
waitTime = 10
|
|
446
488
|
} catch (e) {
|
|
@@ -471,5 +513,25 @@ async function braid_fetch_wrapper(url, params) {
|
|
|
471
513
|
}
|
|
472
514
|
|
|
473
515
|
function v_eq(v1, v2) {
|
|
474
|
-
return v1.length
|
|
516
|
+
return v1.length === v2?.length && v1.every((x, i) => x == v2[i])
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async function is_read_only(fullpath) {
|
|
520
|
+
const stats = await require('fs').promises.stat(fullpath)
|
|
521
|
+
return require('os').platform() === "win32" ?
|
|
522
|
+
!!(stats.mode & 0x1) :
|
|
523
|
+
!(stats.mode & 0o200)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function set_read_only(fullpath, read_only) {
|
|
527
|
+
if (require('os').platform() === "win32") {
|
|
528
|
+
await new Promise((resolve, reject) => {
|
|
529
|
+
require("child_process").exec(`fsutil file setattr readonly "${fullpath}" ${!!read_only}`, (error) => error ? reject(error) : resolve())
|
|
530
|
+
})
|
|
531
|
+
} else {
|
|
532
|
+
let mode = (await require('fs').promises.stat(fullpath)).mode
|
|
533
|
+
if (read_only) mode &= ~0o222
|
|
534
|
+
else mode |= 0o200
|
|
535
|
+
await require('fs').promises.chmod(fullpath, mode)
|
|
536
|
+
}
|
|
475
537
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "braidfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "braid technology synchronizing files and webpages",
|
|
5
5
|
"author": "Braid Working Group",
|
|
6
6
|
"repository": "braid-org/braidfs",
|
|
7
7
|
"homepage": "https://braid.org",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"braid-http": "^0.3.20",
|
|
10
|
-
"braid-text": "^0.0.
|
|
10
|
+
"braid-text": "^0.0.27",
|
|
11
11
|
"chokidar": "^3.6.0"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|