braidfs 0.0.13 → 0.0.15

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 +186 -201
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -1,15 +1,18 @@
1
1
 
2
2
  let http = require('http');
3
-
4
3
  let { diff_main } = require('./diff.js')
5
4
  let braid_text = require("braid-text");
6
5
  let braid_fetch = require('braid-http').fetch
7
6
 
7
+ process.on("unhandledRejection", (x) => console.log(`unhandledRejection: ${x.stack}`))
8
+ process.on("uncaughtException", (x) => console.log(`uncaughtException: ${x.stack}`))
9
+
8
10
  let port = 10000
9
11
  let cookie = null
10
12
  let pin_urls = []
11
13
  let pindex_urls = []
12
14
  let proxy_base = `./proxy_base`
15
+ let proxy_base_support = `./proxy_base_support`
13
16
 
14
17
  let argv = process.argv.slice(2)
15
18
  while (argv.length) {
@@ -30,8 +33,19 @@ while (argv.length) {
30
33
  }
31
34
  console.log({ pin_urls, pindex_urls })
32
35
 
33
- process.on("unhandledRejection", (x) => console.log(`unhandledRejection: ${x.stack}`))
34
- process.on("uncaughtException", (x) => console.log(`uncaughtException: ${x.stack}`))
36
+ for (let url of pin_urls) proxy_url(url)
37
+ pindex_urls.forEach(async url => {
38
+ let prefix = new URL(url).origin
39
+ while (true) {
40
+ let urls = await (await fetch(url)).json()
41
+ for (let url of urls) proxy_url(prefix + url)
42
+ await new Promise(done => setTimeout(done, 1000 * 60 * 60))
43
+ }
44
+ })
45
+
46
+ braid_text.list().then(x => {
47
+ for (let xx of x) proxy_url(xx)
48
+ })
35
49
 
36
50
  const server = http.createServer(async (req, res) => {
37
51
  console.log(`${req.method} ${req.url}`);
@@ -71,7 +85,7 @@ const server = http.createServer(async (req, res) => {
71
85
  proxy_url(url)
72
86
 
73
87
  // Now serve the collaborative text!
74
- braid_text.serve(req, res, { key: url })
88
+ braid_text.serve(req, res, { key: normalize_url(url) })
75
89
  });
76
90
 
77
91
  server.listen(port, () => {
@@ -79,22 +93,12 @@ server.listen(port, () => {
79
93
  console.log('This proxy is only accessible from localhost');
80
94
  });
81
95
 
82
- for (let url of pin_urls) proxy_url(url)
83
- pindex_urls.forEach(async url => {
84
- let prefix = new URL(url).origin
85
- while (true) {
86
- let urls = await (await fetch(url)).json()
87
- for (let url of urls) proxy_url(prefix + url)
88
- await new Promise(done => setTimeout(done, 1000 * 60 * 60))
89
- }
90
- })
91
-
92
- braid_text.list().then(x => {
93
- for (let xx of x) proxy_url(xx)
94
- })
95
-
96
96
  ////////////////////////////////
97
97
 
98
+ function normalize_url(url) {
99
+ return url.replace(/(\/index|\/)+$/, '')
100
+ }
101
+
98
102
  async function proxy_url(url) {
99
103
  let chain = proxy_url.chain || (proxy_url.chain = Promise.resolve())
100
104
 
@@ -115,7 +119,7 @@ async function proxy_url(url) {
115
119
  await require("fs").promises.mkdir(path, { recursive: true })
116
120
 
117
121
  while (await is_dir(partial))
118
- partial = require("path").join(partial, 'index.html')
122
+ partial = require("path").join(partial, 'index')
119
123
 
120
124
  await require("fs").promises.writeFile(partial, save)
121
125
  break
@@ -125,8 +129,8 @@ async function proxy_url(url) {
125
129
  }))
126
130
  }
127
131
 
128
- // normalize url by removing any trailing /index.html/index.html/
129
- let normalized_url = url.replace(/(\/index\.html|\/)+$/, '')
132
+ // normalize url by removing any trailing /index/index/
133
+ let normalized_url = normalize_url(url)
130
134
  let wasnt_normal = normalized_url != url
131
135
  url = normalized_url
132
136
 
@@ -134,18 +138,114 @@ async function proxy_url(url) {
134
138
  if (proxy_url.cache[url]) return
135
139
  proxy_url.cache[url] = true
136
140
 
141
+ console.log(`proxy_url: ${url}`)
142
+
137
143
  let path = url.replace(/^https?:\/\//, '')
138
144
  let fullpath = require("path").join(proxy_base, path)
139
145
 
140
- // if we're accessing /blah/index.html, it will be normalized to /blah,
146
+ // if we're accessing /blah/index, it will be normalized to /blah,
141
147
  // but we still want to create a directory out of blah in this case
142
- if (wasnt_normal && !(await is_dir(fullpath))) ensure_path(fullpath)
148
+ if (wasnt_normal && !(await is_dir(fullpath))) await ensure_path(fullpath)
143
149
 
144
- let last_text = ''
150
+ await ensure_path(require("path").dirname(fullpath))
145
151
 
146
- console.log(`proxy_url: ${url}`)
152
+ await require("fs").promises.mkdir(proxy_base_support, { recursive: true })
153
+
154
+ async function get_fullpath() {
155
+ let p = fullpath
156
+ while (await is_dir(p)) p = require("path").join(p, 'index')
157
+ return p
158
+ }
147
159
 
148
160
  let peer = Math.random().toString(36).slice(2)
161
+ var char_counter = -1
162
+ let file_last_version = null
163
+ let file_last_text = null
164
+ let file_needs_reading = true
165
+ let file_needs_writing = null
166
+ let file_loop_pump_lock = 0
167
+
168
+ function signal_file_needs_reading() {
169
+ file_needs_reading = true
170
+ file_loop_pump()
171
+ }
172
+
173
+ function signal_file_needs_writing() {
174
+ file_needs_writing = true
175
+ file_loop_pump()
176
+ }
177
+
178
+ async function send_out(stuff) {
179
+ await braid_fetch_wrapper(url, {
180
+ headers: {
181
+ "Merge-Type": "dt",
182
+ "Content-Type": 'text/plain',
183
+ ...(cookie ? { "Cookie": cookie } : {}),
184
+ },
185
+ method: "PUT",
186
+ retry: true,
187
+ ...stuff
188
+ })
189
+ }
190
+
191
+ file_loop_pump()
192
+ async function file_loop_pump() {
193
+ if (file_loop_pump_lock) return
194
+ file_loop_pump_lock++
195
+
196
+ if (file_last_version === null) {
197
+ try {
198
+ file_last_version = JSON.parse(await require('fs').promises.readFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), { encoding: 'utf8' }))
199
+ file_last_text = (await braid_text.get(url, { version: file_last_version })).body
200
+ file_needs_writing = !v_eq(file_last_version, (await braid_text.get(url, {})).version)
201
+ } catch (e) {
202
+ file_last_version = []
203
+ file_last_text = ''
204
+ file_needs_writing = true
205
+ }
206
+ }
207
+
208
+ while (file_needs_reading || file_needs_writing) {
209
+ if (file_needs_reading) {
210
+ file_needs_reading = false
211
+
212
+ let text = ''
213
+ try { text = await require('fs').promises.readFile(await get_fullpath(), { encoding: 'utf8' }) } catch (e) { }
214
+
215
+ var patches = diff(file_last_text, text)
216
+ if (patches.length) {
217
+ // convert from js-indicies to code-points
218
+ char_counter += patches_to_code_points(patches, file_last_text)
219
+
220
+ file_last_text = text
221
+
222
+ var version = [peer + "-" + char_counter]
223
+ var parents = file_last_version
224
+ file_last_version = version
225
+
226
+ send_out({ version, parents, patches, peer })
227
+
228
+ await braid_text.put(url, { version, parents, patches, peer })
229
+
230
+ await require('fs').promises.writeFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), JSON.stringify(file_last_version))
231
+ }
232
+ }
233
+ if (file_needs_writing) {
234
+ file_needs_writing = false
235
+
236
+ console.log(`writing file ${await get_fullpath()}`)
237
+
238
+ let { version, body } = await braid_text.get(url, {})
239
+ if (!v_eq(version, file_last_version)) {
240
+ file_last_version = version
241
+ file_last_text = body
242
+ await require('fs').promises.writeFile(await get_fullpath(), file_last_text)
243
+ await require('fs').promises.writeFile(require('path').join(proxy_base_support, braid_text.encode_filename(url)), JSON.stringify(file_last_version))
244
+ }
245
+ }
246
+ }
247
+ file_loop_pump_lock--
248
+ }
149
249
 
150
250
  braid_fetch_wrapper(url, {
151
251
  headers: {
@@ -160,81 +260,18 @@ async function proxy_url(url) {
160
260
  },
161
261
  peer
162
262
  }).then(x => {
163
- x.subscribe(update => {
263
+ x.subscribe(async update => {
164
264
  // console.log(`update: ${JSON.stringify(update, null, 4)}`)
165
265
  if (update.version.length == 0) return;
166
266
 
167
- braid_text.put(url, { ...update, peer })
168
- })
169
- })
170
-
171
- // try a HEAD without subscribe to get the version
172
- braid_fetch_wrapper(url, {
173
- method: 'HEAD',
174
- headers: { Accept: 'text/plain' },
175
- retry: true,
176
- }).then(async head_res => {
177
- let parents = head_res.headers.get('version') ?
178
- JSON.parse(`[${head_res.headers.get('version')}]`) :
179
- null
267
+ await braid_text.put(url, { ...update, peer })
180
268
 
181
- // now get everything since then, and send it back..
182
- braid_text.get(url, {
183
- parents,
184
- merge_type: 'dt',
185
- peer,
186
- subscribe: async ({ version, parents, body, patches }) => {
187
- if (version.length == 0) return;
188
-
189
- // console.log(`local got: ${JSON.stringify({ version, parents, body, patches }, null, 4)}`)
190
- // console.log(`cookie = ${cookie}`)
191
-
192
- await braid_fetch_wrapper(url, {
193
- headers: {
194
- "Merge-Type": "dt",
195
- "Content-Type": 'text/plain',
196
- ...(cookie ? { "Cookie": cookie } : {}),
197
- },
198
- method: "PUT",
199
- retry: true,
200
- version, parents, body, patches,
201
- peer
202
- })
203
- },
269
+ signal_file_needs_writing()
204
270
  })
205
271
  })
206
272
 
207
- await ensure_path(require("path").dirname(fullpath))
208
-
209
- async function get_fullpath() {
210
- let p = fullpath
211
- while (await is_dir(p)) p = require("path").join(p, 'index.html')
212
- return p
213
- }
214
-
215
- let simpleton = simpleton_client(url, {
216
- apply_remote_update: async ({ state, patches }) => {
217
- return await (chain = chain.then(async () => {
218
- console.log(`writing file ${await get_fullpath()}`)
219
-
220
- if (state !== undefined) last_text = state
221
- else last_text = apply_patches(last_text, patches)
222
- await require('fs').promises.writeFile(await get_fullpath(), last_text)
223
- return last_text
224
- }))
225
- },
226
- generate_local_diff_update: async (_) => {
227
- return await (chain = chain.then(async () => {
228
- let text = await require('fs').promises.readFile(await get_fullpath(), { encoding: 'utf8' })
229
- var patches = diff(last_text, text)
230
- last_text = text
231
- return patches.length ? { patches, new_state: last_text } : null
232
- }))
233
- }
234
- })
235
-
236
273
  if (!proxy_url.path_to_func) proxy_url.path_to_func = {}
237
- proxy_url.path_to_func[path] = () => simpleton.changed()
274
+ proxy_url.path_to_func[path] = signal_file_needs_reading
238
275
 
239
276
  if (!proxy_url.chokidar) {
240
277
  proxy_url.chokidar = true
@@ -242,12 +279,44 @@ async function proxy_url(url) {
242
279
  path = require('path').relative(proxy_base, path)
243
280
  console.log(`path changed: ${path}`)
244
281
 
245
- path = path.replace(/(\/index\.html|\/)+$/, '')
246
- console.log(`normalized path: ${path}`)
282
+ path = normalize_url(path)
283
+ // console.log(`normalized path: ${path}`)
247
284
 
248
285
  proxy_url.path_to_func[path]()
249
286
  });
250
287
  }
288
+
289
+ // try a HEAD without subscribe to get the version
290
+ let parents = null
291
+ try {
292
+ let head_res = await braid_fetch_wrapper(url, {
293
+ method: 'HEAD',
294
+ headers: { Accept: 'text/plain' },
295
+ retry: true,
296
+ })
297
+ parents = head_res.headers.get('version') ?
298
+ JSON.parse(`[${head_res.headers.get('version')}]`) :
299
+ null
300
+ } catch (e) {
301
+ console.log('HEAD failed: ', e)
302
+ }
303
+
304
+ // now get everything since then, and send it back..
305
+ braid_text.get(url, {
306
+ parents,
307
+ merge_type: 'dt',
308
+ peer,
309
+ subscribe: async ({ version, parents, body, patches }) => {
310
+ if (version.length == 0) return;
311
+
312
+ // console.log(`local got: ${JSON.stringify({ version, parents, body, patches }, null, 4)}`)
313
+ // console.log(`cookie = ${cookie}`)
314
+
315
+ signal_file_needs_writing()
316
+
317
+ send_out({ version, parents, body, patches, peer })
318
+ },
319
+ })
251
320
  }
252
321
 
253
322
  async function is_dir(p) {
@@ -291,118 +360,30 @@ function free_the_cors(req, res) {
291
360
  }
292
361
  }
293
362
 
294
- function apply_patches(originalString, patches) {
295
- let offset = 0;
296
- for (let p of patches) {
297
- p.range[0] += offset;
298
- p.range[1] += offset;
299
- offset -= p.range[1] - p.range[0];
300
- offset += p.content.length;
301
- }
302
-
303
- let result = originalString;
304
-
363
+ function patches_to_code_points(patches, prev_state) {
364
+ let char_counter = 0
365
+ let c = 0
366
+ let i = 0
305
367
  for (let p of patches) {
306
- let range = p.range;
307
- result =
308
- result.substring(0, range[0]) +
309
- p.content +
310
- result.substring(range[1]);
311
- }
312
-
313
- return result;
314
- }
315
-
316
- function simpleton_client(url, { apply_remote_update, generate_local_diff_update, content_type }) {
317
- var peer = Math.random().toString(36).slice(2)
318
- var current_version = []
319
- var prev_state = ""
320
- var char_counter = -1
321
- var chain = Promise.resolve()
322
- var queued_changes = 0
323
-
324
- braid_text.get(url, {
325
- peer,
326
- subscribe: (update) => {
327
- chain = chain.then(async () => {
328
- // Only accept the update if its parents == our current version
329
- update.parents.sort()
330
- if (current_version.length === update.parents.length
331
- && current_version.every((v, i) => v === update.parents[i])) {
332
- current_version = update.version.sort()
333
- update.state = update.body
334
-
335
- if (update.patches) {
336
- for (let p of update.patches) p.range = p.range.match(/\d+/g).map((x) => 1 * x)
337
- update.patches.sort((a, b) => a.range[0] - b.range[0])
338
-
339
- // convert from code-points to js-indicies
340
- let c = 0
341
- let i = 0
342
- for (let p of update.patches) {
343
- while (c < p.range[0]) {
344
- i += get_char_size(prev_state, i)
345
- c++
346
- }
347
- p.range[0] = i
348
-
349
- while (c < p.range[1]) {
350
- i += get_char_size(prev_state, i)
351
- c++
352
- }
353
- p.range[1] = i
354
- }
355
- }
356
-
357
- prev_state = await apply_remote_update(update)
358
- }
359
- })
368
+ while (i < p.range[0]) {
369
+ i += get_char_size(prev_state, i)
370
+ c++
360
371
  }
361
- })
362
-
363
- return {
364
- changed: () => {
365
- if (queued_changes) return
366
- queued_changes++
367
- chain = chain.then(async () => {
368
- queued_changes--
369
- var update = await generate_local_diff_update(prev_state)
370
- if (!update) return // Stop if there wasn't a change!
371
- var { patches, new_state } = update
372
-
373
- // convert from js-indicies to code-points
374
- let c = 0
375
- let i = 0
376
- for (let p of patches) {
377
- while (i < p.range[0]) {
378
- i += get_char_size(prev_state, i)
379
- c++
380
- }
381
- p.range[0] = c
382
-
383
- while (i < p.range[1]) {
384
- i += get_char_size(prev_state, i)
385
- c++
386
- }
387
- p.range[1] = c
372
+ p.range[0] = c
388
373
 
389
- char_counter += p.range[1] - p.range[0]
390
- char_counter += count_code_points(p.content)
391
-
392
- p.unit = "text"
393
- p.range = `[${p.range[0]}:${p.range[1]}]`
394
- }
395
-
396
- var version = [peer + "-" + char_counter]
374
+ while (i < p.range[1]) {
375
+ i += get_char_size(prev_state, i)
376
+ c++
377
+ }
378
+ p.range[1] = c
397
379
 
398
- var parents = current_version
399
- current_version = version
400
- prev_state = new_state
380
+ char_counter += p.range[1] - p.range[0]
381
+ char_counter += count_code_points(p.content)
401
382
 
402
- braid_text.put(url, { version, parents, patches, peer })
403
- })
404
- }
383
+ p.unit = "text"
384
+ p.range = `[${p.range[0]}:${p.range[1]}]`
405
385
  }
386
+ return char_counter
406
387
  }
407
388
 
408
389
  function get_char_size(s, i) {
@@ -456,4 +437,8 @@ async function braid_fetch_wrapper(url, params) {
456
437
  }
457
438
  })
458
439
  }
459
- }
440
+ }
441
+
442
+ function v_eq(v1, v2) {
443
+ return v1.length == v2.length && v1.every((x, i) => x == v2[i])
444
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
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.22",
10
+ "braid-text": "^0.0.25",
11
11
  "chokidar": "^3.6.0"
12
12
  }
13
13
  }