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.
Files changed (2) hide show
  1. package/index.js +91 -29
  2. 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
- proxy_url.cache[url] = true
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
- if (!proxy_url.path_to_func) proxy_url.path_to_func = {}
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: { Accept: 'text/plain' },
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 == v2.length && v1.every((x, i) => x == v2[i])
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.22",
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.26",
10
+ "braid-text": "^0.0.27",
11
11
  "chokidar": "^3.6.0"
12
12
  },
13
13
  "bin": {