braid-blob 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.
- package/index.js +217 -176
- package/package.json +1 -1
- package/test/server.js +43 -0
- package/test/test.html +754 -0
package/index.js
CHANGED
|
@@ -1,203 +1,244 @@
|
|
|
1
|
-
|
|
2
1
|
var {http_server: braidify, free_cors} = require('braid-http'),
|
|
3
2
|
fs = require('fs'),
|
|
4
3
|
path = require('path')
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
5
|
+
function create_braid_blob() {
|
|
6
|
+
var braid_blob = {
|
|
7
|
+
db_folder: './braid-blob-db',
|
|
8
|
+
cache: {},
|
|
9
|
+
key_to_subs: {},
|
|
10
|
+
peer: null // we'll try to load this from a file, if not set by the user
|
|
11
|
+
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
braid_blob.init = async () => {
|
|
14
|
+
braid_blob.init = () => {}
|
|
13
15
|
|
|
14
|
-
braid_blob.
|
|
15
|
-
|
|
16
|
+
await fs.promises.mkdir(`${braid_blob.db_folder}/blob`, { recursive: true })
|
|
17
|
+
await fs.promises.mkdir(`${braid_blob.db_folder}/meta`, { recursive: true })
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
// establish a peer id
|
|
20
|
+
if (!braid_blob.peer)
|
|
21
|
+
try {
|
|
22
|
+
braid_blob.peer = await fs.promises.readFile(`${braid_blob.db_folder}/peer.txt`, 'utf8')
|
|
23
|
+
} catch (e) {}
|
|
24
|
+
if (!braid_blob.peer)
|
|
25
|
+
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
26
|
+
await fs.promises.writeFile(`${braid_blob.db_folder}/peer.txt`, braid_blob.peer)
|
|
27
|
+
}
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
braid_blob.serve = async (req, res, options = {}) => {
|
|
30
|
+
await braid_blob.init()
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
var body = req.method === 'PUT' && await slurp(req)
|
|
32
|
+
if (!options.key) options.key = decodeURIComponent(req.url.split('?')[0])
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const metaname = `${braid_blob.meta_folder}/${encode_filename(options.key)}`
|
|
29
|
-
|
|
30
|
-
// Read the meta file
|
|
31
|
-
var meta = {}
|
|
32
|
-
try {
|
|
33
|
-
meta = JSON.parse(await fs.promises.readFile(metaname, 'utf8'))
|
|
34
|
-
} catch (e) {}
|
|
35
|
-
var our_v = meta.version
|
|
36
|
-
|
|
37
|
-
if (req.method === 'GET') {
|
|
38
|
-
// Handle GET request for binary files
|
|
39
|
-
|
|
40
|
-
if (our_v == null) {
|
|
41
|
-
res.statusCode = 404
|
|
42
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
43
|
-
return res.end('File Not Found')
|
|
44
|
-
}
|
|
34
|
+
braidify(req, res)
|
|
35
|
+
if (res.is_multiplexer) return
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
res.statusCode = 406
|
|
49
|
-
res.setHeader('Content-Type', 'text/plain')
|
|
50
|
-
return res.end(`Content-Type of ${meta.content_type} not in Accept: ${req.headers.accept}`)
|
|
51
|
-
}
|
|
37
|
+
// Handle OPTIONS request
|
|
38
|
+
if (req.method === 'OPTIONS') return res.end();
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!req.subscribe)
|
|
63
|
-
return res.end(await fs.promises.readFile(filename))
|
|
64
|
-
|
|
65
|
-
if (!res.hasHeader("editable"))
|
|
66
|
-
res.setHeader("Editable", "true")
|
|
67
|
-
|
|
68
|
-
// Start a subscription for future updates.
|
|
69
|
-
if (!key_to_subs[options.key]) key_to_subs[options.key] = new Map()
|
|
70
|
-
var peer = req.peer || Math.random().toString(36).slice(2)
|
|
71
|
-
key_to_subs[options.key].set(peer, res)
|
|
72
|
-
|
|
73
|
-
res.startSubscription({ onClose: () => {
|
|
74
|
-
key_to_subs[options.key].delete(peer)
|
|
75
|
-
if (!key_to_subs[options.key].size)
|
|
76
|
-
delete key_to_subs[options.key]
|
|
77
|
-
}})
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// Send an immediate update when:
|
|
81
|
-
if (!req.parents || // 1) They have no version history
|
|
82
|
-
// (need full sync)
|
|
83
|
-
!req.parents.length || // 2) Or their version is the empty set
|
|
84
|
-
our_v > 1*req.parents[0] // 3) Or our version is newer
|
|
85
|
-
)
|
|
86
|
-
return res.sendUpdate({
|
|
87
|
-
version: our_v != null ? ['' + our_v] : [],
|
|
88
|
-
body: our_v != null ? await fs.promises.readFile(filename) : ''
|
|
89
|
-
})
|
|
90
|
-
else res.write('\n\n') // get the node http code to send headers
|
|
91
|
-
} else if (req.method === 'PUT') {
|
|
92
|
-
// Handle PUT request to update binary files
|
|
93
|
-
|
|
94
|
-
// Ensure directory exists
|
|
95
|
-
await fs.promises.mkdir(path.dirname(filename), { recursive: true })
|
|
96
|
-
await fs.promises.mkdir(path.dirname(metaname), { recursive: true })
|
|
97
|
-
|
|
98
|
-
var their_v =
|
|
99
|
-
!req.version ?
|
|
100
|
-
// we'll give them a version in this case
|
|
101
|
-
Math.max(our_v != null ? our_v + 1 : 0, Date.now()) :
|
|
102
|
-
!req.version.length ?
|
|
103
|
-
null :
|
|
104
|
-
1*req.version[0]
|
|
105
|
-
|
|
106
|
-
if (their_v != null &&
|
|
107
|
-
(our_v == null || their_v > our_v)) {
|
|
108
|
-
|
|
109
|
-
// Write the file
|
|
110
|
-
await fs.promises.writeFile(filename, body)
|
|
111
|
-
|
|
112
|
-
// Write the meta file
|
|
113
|
-
meta.version = their_v
|
|
114
|
-
if (req.headers['content-type'])
|
|
115
|
-
meta.content_type = req.headers['content-type']
|
|
116
|
-
await fs.promises.writeFile(metaname, JSON.stringify(meta))
|
|
117
|
-
|
|
118
|
-
// Notify all subscriptions of the update
|
|
119
|
-
// (except the peer which made the PUT request itself)
|
|
120
|
-
if (key_to_subs[options.key])
|
|
121
|
-
for (var [peer, sub] of key_to_subs[options.key].entries())
|
|
122
|
-
if (peer !== req.peer)
|
|
123
|
-
sub.sendUpdate({ body, version: ['' + their_v] })
|
|
124
|
-
|
|
125
|
-
res.setHeader("Version", `"${their_v}"`)
|
|
126
|
-
} else {
|
|
127
|
-
res.setHeader("Version", our_v != null ? `"${our_v}"` : '')
|
|
128
|
-
}
|
|
129
|
-
res.end('')
|
|
130
|
-
} else if (req.method === 'DELETE') {
|
|
131
|
-
try {
|
|
132
|
-
await fs.promises.unlink(filename)
|
|
133
|
-
} catch (e) {}
|
|
40
|
+
// consume PUT body
|
|
41
|
+
var body = req.method === 'PUT' && await slurp(req)
|
|
42
|
+
|
|
43
|
+
await within_fiber(options.key, async () => {
|
|
44
|
+
const filename = `${braid_blob.db_folder}/blob/${encode_filename(options.key)}`
|
|
45
|
+
const metaname = `${braid_blob.db_folder}/meta/${encode_filename(options.key)}`
|
|
46
|
+
|
|
47
|
+
// Read the meta file
|
|
48
|
+
var meta = {}
|
|
134
49
|
try {
|
|
135
|
-
await fs.promises.
|
|
50
|
+
meta = JSON.parse(await fs.promises.readFile(metaname, 'utf8'))
|
|
136
51
|
} catch (e) {}
|
|
137
|
-
res.statusCode = 204 // No Content
|
|
138
|
-
res.end('')
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
52
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
53
|
+
if (req.method === 'GET') {
|
|
54
|
+
// Handle GET request for binary files
|
|
55
|
+
|
|
56
|
+
if (meta.event == null) {
|
|
57
|
+
res.statusCode = 404
|
|
58
|
+
res.setHeader('Content-Type', 'text/plain')
|
|
59
|
+
return res.end('File Not Found')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (meta.content_type && req.headers.accept &&
|
|
63
|
+
!isAcceptable(meta.content_type, req.headers.accept)) {
|
|
64
|
+
res.statusCode = 406
|
|
65
|
+
res.setHeader('Content-Type', 'text/plain')
|
|
66
|
+
return res.end(`Content-Type of ${meta.content_type} not in Accept: ${req.headers.accept}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Set Version header;
|
|
70
|
+
// but if this is a subscription,
|
|
71
|
+
// then we set Current-Version instead
|
|
72
|
+
res.setHeader((req.subscribe ? 'Current-' : '') + 'Version',
|
|
73
|
+
JSON.stringify(meta.event))
|
|
74
|
+
|
|
75
|
+
// Set Content-Type
|
|
76
|
+
if (meta.content_type)
|
|
77
|
+
res.setHeader('Content-Type', meta.content_type)
|
|
78
|
+
|
|
79
|
+
if (!req.subscribe)
|
|
80
|
+
return res.end(await fs.promises.readFile(filename))
|
|
81
|
+
|
|
82
|
+
if (!res.hasHeader("editable"))
|
|
83
|
+
res.setHeader("Editable", "true")
|
|
84
|
+
|
|
85
|
+
// Start a subscription for future updates.
|
|
86
|
+
if (!braid_blob.key_to_subs[options.key])
|
|
87
|
+
braid_blob.key_to_subs[options.key] = new Map()
|
|
88
|
+
var peer = req.peer || Math.random().toString(36).slice(2)
|
|
89
|
+
braid_blob.key_to_subs[options.key].set(peer, res)
|
|
90
|
+
|
|
91
|
+
res.startSubscription({ onClose: () => {
|
|
92
|
+
braid_blob.key_to_subs[options.key].delete(peer)
|
|
93
|
+
if (!braid_blob.key_to_subs[options.key].size)
|
|
94
|
+
delete braid_blob.key_to_subs[options.key]
|
|
95
|
+
}})
|
|
96
|
+
|
|
97
|
+
// Send an immediate update when:
|
|
98
|
+
if (!req.parents || // 1) They want everything,
|
|
99
|
+
!req.parents.length || // 2) Or everything past the empty set,
|
|
100
|
+
compare_events(meta.event, req.parents[0]) > 0
|
|
101
|
+
// 3) Or what we have is newer
|
|
102
|
+
)
|
|
103
|
+
return res.sendUpdate({
|
|
104
|
+
version: [meta.event],
|
|
105
|
+
'Merge-Type': 'lww',
|
|
106
|
+
body: await fs.promises.readFile(filename)
|
|
107
|
+
})
|
|
108
|
+
else res.write('\n\n') // get the node http code to send headers
|
|
109
|
+
} else if (req.method === 'PUT') {
|
|
110
|
+
// Handle PUT request to update binary files
|
|
111
|
+
|
|
112
|
+
var their_e =
|
|
113
|
+
!req.version ?
|
|
114
|
+
// we'll give them a event id in this case
|
|
115
|
+
`${braid_blob.peer}-${Math.max(Date.now(),
|
|
116
|
+
meta.event ? 1*get_event_seq(meta.event) + 1 : -Infinity)}` :
|
|
117
|
+
!req.version.length ?
|
|
118
|
+
null :
|
|
119
|
+
req.version[0]
|
|
120
|
+
|
|
121
|
+
if (their_e != null &&
|
|
122
|
+
(meta.event == null ||
|
|
123
|
+
compare_events(their_e, meta.event) > 0)) {
|
|
124
|
+
meta.event = their_e
|
|
125
|
+
|
|
126
|
+
// Write the file
|
|
127
|
+
await fs.promises.writeFile(filename, body)
|
|
128
|
+
|
|
129
|
+
// Write the meta file
|
|
130
|
+
if (req.headers['content-type'])
|
|
131
|
+
meta.content_type = req.headers['content-type']
|
|
132
|
+
await fs.promises.writeFile(metaname, JSON.stringify(meta))
|
|
133
|
+
|
|
134
|
+
// Notify all subscriptions of the update
|
|
135
|
+
// (except the peer which made the PUT request itself)
|
|
136
|
+
if (braid_blob.key_to_subs[options.key])
|
|
137
|
+
for (var [peer, sub] of braid_blob.key_to_subs[options.key].entries())
|
|
138
|
+
if (peer !== req.peer)
|
|
139
|
+
sub.sendUpdate({
|
|
140
|
+
version: [meta.event],
|
|
141
|
+
'Merge-Type': 'lww',
|
|
142
|
+
body
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
res.setHeader("Version", meta.event != null ? JSON.stringify(meta.event) : '')
|
|
146
|
+
res.end('')
|
|
147
|
+
} else if (req.method === 'DELETE') {
|
|
148
|
+
try {
|
|
149
|
+
await fs.promises.unlink(filename)
|
|
150
|
+
} catch (e) {}
|
|
151
|
+
try {
|
|
152
|
+
await fs.promises.unlink(metaname)
|
|
153
|
+
} catch (e) {}
|
|
154
|
+
res.statusCode = 204 // No Content
|
|
155
|
+
res.end('')
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
146
159
|
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
function compare_events(a, b) {
|
|
161
|
+
var a_num = get_event_seq(a)
|
|
162
|
+
var b_num = get_event_seq(b)
|
|
149
163
|
|
|
150
|
-
|
|
151
|
-
|
|
164
|
+
var c = a_num.length - b_num.length
|
|
165
|
+
if (c) return c
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
var prev = within_fiber.chains[id] || Promise.resolve()
|
|
156
|
-
var curr = prev.then(async () => {
|
|
157
|
-
try {
|
|
158
|
-
return await func()
|
|
159
|
-
} finally {
|
|
160
|
-
if (within_fiber.chains[id] === curr)
|
|
161
|
-
delete within_fiber.chains[id]
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
return within_fiber.chains[id] = curr
|
|
165
|
-
}
|
|
167
|
+
var c = a_num.localeCompare(b_num)
|
|
168
|
+
if (c) return c
|
|
166
169
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
return a.localeCompare(b)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function get_event_seq(e) {
|
|
174
|
+
for (let i = e.length - 1; i >= 0; i--)
|
|
175
|
+
if (e[i] === '-') return e.slice(i + 1)
|
|
176
|
+
return e
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function encode_filename(filename) {
|
|
180
|
+
// Swap all "!" and "/" characters
|
|
181
|
+
let swapped = filename.replace(/[!/]/g, (match) => (match === "!" ? "/" : "!"))
|
|
182
|
+
|
|
183
|
+
// Encode the filename using encodeURIComponent()
|
|
184
|
+
let encoded = encodeURIComponent(swapped)
|
|
185
|
+
|
|
186
|
+
return encoded
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function within_fiber(id, func) {
|
|
190
|
+
if (!within_fiber.chains) within_fiber.chains = {}
|
|
191
|
+
var prev = within_fiber.chains[id] || Promise.resolve()
|
|
192
|
+
var curr = prev.then(async () => {
|
|
193
|
+
try {
|
|
194
|
+
return await func()
|
|
195
|
+
} finally {
|
|
196
|
+
if (within_fiber.chains[id] === curr)
|
|
197
|
+
delete within_fiber.chains[id]
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
return within_fiber.chains[id] = curr
|
|
201
|
+
}
|
|
174
202
|
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
203
|
+
async function slurp(req) {
|
|
204
|
+
return await new Promise(done => {
|
|
205
|
+
var chunks = []
|
|
206
|
+
req.on('data', chunk => chunks.push(chunk))
|
|
207
|
+
req.on('end', () => done(Buffer.concat(chunks)))
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isAcceptable(contentType, acceptHeader) {
|
|
212
|
+
// If no Accept header or Accept is */*, accept everything
|
|
213
|
+
if (!acceptHeader || acceptHeader === '*/*') return true;
|
|
185
214
|
|
|
186
|
-
//
|
|
187
|
-
|
|
215
|
+
// Parse the Accept header into individual media types
|
|
216
|
+
const acceptTypes = acceptHeader.split(',').map(type => type.trim());
|
|
188
217
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
218
|
+
for (const acceptType of acceptTypes) {
|
|
219
|
+
// Remove quality values (e.g., "text/html;q=0.9" -> "text/html")
|
|
220
|
+
const cleanAcceptType = acceptType.split(';')[0].trim();
|
|
221
|
+
|
|
222
|
+
// Exact match
|
|
223
|
+
if (cleanAcceptType === contentType) return true;
|
|
224
|
+
|
|
225
|
+
// Wildcard subtype match (e.g., "image/*" matches "image/png")
|
|
226
|
+
if (cleanAcceptType.endsWith('/*')) {
|
|
227
|
+
const acceptMain = cleanAcceptType.slice(0, -2);
|
|
228
|
+
const contentMain = contentType.split('/')[0];
|
|
229
|
+
if (acceptMain === contentMain) return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Full wildcard
|
|
233
|
+
if (cleanAcceptType === '*/*') return true;
|
|
194
234
|
}
|
|
195
235
|
|
|
196
|
-
|
|
197
|
-
if (cleanAcceptType === '*/*') return true;
|
|
236
|
+
return false;
|
|
198
237
|
}
|
|
199
|
-
|
|
200
|
-
|
|
238
|
+
|
|
239
|
+
braid_blob.create_braid_blob = create_braid_blob
|
|
240
|
+
|
|
241
|
+
return braid_blob
|
|
201
242
|
}
|
|
202
243
|
|
|
203
|
-
module.exports =
|
|
244
|
+
module.exports = create_braid_blob()
|
package/package.json
CHANGED
package/test/server.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
var port = process.argv[2] || 8889
|
|
3
|
+
|
|
4
|
+
var braid_blob = require(`${__dirname}/../index.js`)
|
|
5
|
+
var {free_cors} = require("braid-http")
|
|
6
|
+
braid_blob.db_folder = `${__dirname}/test_db_folder`
|
|
7
|
+
|
|
8
|
+
var server = require("http").createServer(async (req, res) => {
|
|
9
|
+
console.log(`${req.method} ${req.url}`)
|
|
10
|
+
|
|
11
|
+
// Free the CORS
|
|
12
|
+
free_cors(res)
|
|
13
|
+
if (req.method === 'OPTIONS') return
|
|
14
|
+
|
|
15
|
+
if (req.url.startsWith('/eval')) {
|
|
16
|
+
var body = await new Promise(done => {
|
|
17
|
+
var chunks = []
|
|
18
|
+
req.on('data', chunk => chunks.push(chunk))
|
|
19
|
+
req.on('end', () => done(Buffer.concat(chunks)))
|
|
20
|
+
})
|
|
21
|
+
try {
|
|
22
|
+
eval('' + body)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' })
|
|
25
|
+
res.end(`Error: ${error.message}`)
|
|
26
|
+
}
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (req.url.startsWith('/test.html')) {
|
|
31
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" })
|
|
32
|
+
require("fs").createReadStream(`${__dirname}/test.html`).pipe(res)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Now serve the collaborative text!
|
|
37
|
+
braid_blob.serve(req, res)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// only listen on 'localhost' for security
|
|
41
|
+
server.listen(port, 'localhost', () => {
|
|
42
|
+
console.log(`serving: http://localhost:${port}/test.html`)
|
|
43
|
+
})
|
package/test/test.html
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
body {
|
|
3
|
+
font-family: Arial, sans-serif;
|
|
4
|
+
max-width: 800px;
|
|
5
|
+
margin: 0 auto;
|
|
6
|
+
padding: 10px;
|
|
7
|
+
}
|
|
8
|
+
.test {
|
|
9
|
+
margin-bottom: 3px;
|
|
10
|
+
padding: 3px;
|
|
11
|
+
}
|
|
12
|
+
.running {
|
|
13
|
+
background-color: #fffde7;
|
|
14
|
+
}
|
|
15
|
+
.passed {
|
|
16
|
+
background-color: #e8f5e9;
|
|
17
|
+
}
|
|
18
|
+
.failed {
|
|
19
|
+
background-color: #ffebee;
|
|
20
|
+
}
|
|
21
|
+
#summaryContainer {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-wrap: wrap;
|
|
24
|
+
gap: 5px;
|
|
25
|
+
margin-bottom: 20px;
|
|
26
|
+
}
|
|
27
|
+
.summaryBox {
|
|
28
|
+
width: 25px;
|
|
29
|
+
height: 25px;
|
|
30
|
+
border: 1px solid #ddd;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
34
|
+
<div id="summaryContainer"></div>
|
|
35
|
+
<div id="testContainer"></div>
|
|
36
|
+
<script type=module>
|
|
37
|
+
|
|
38
|
+
let delay = 0
|
|
39
|
+
|
|
40
|
+
function createTestDiv(testName) {
|
|
41
|
+
const div = document.createElement("div")
|
|
42
|
+
div.className = "test running"
|
|
43
|
+
div.innerHTML = `<span style="font-weight:bold">${testName}: </span><span class="result">Running...</span>`
|
|
44
|
+
testContainer.appendChild(div)
|
|
45
|
+
return div
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function updateTestResult(div, passed, message, got, expected) {
|
|
49
|
+
div.className = `test ${passed ? "passed" : "failed"}`
|
|
50
|
+
|
|
51
|
+
if (passed) {
|
|
52
|
+
div.querySelector(".result").textContent = message
|
|
53
|
+
div.querySelector(".result").style.fontSize = message.length > 400 ? 'xx-small' : message.length > 100 ? 'small' : ''
|
|
54
|
+
} else {
|
|
55
|
+
div.querySelector(".result").innerHTML = `${message}<br><strong>Got:</strong> ${got}<br><strong>Expected:</strong> ${expected}`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createSummaryBox() {
|
|
60
|
+
var summaryContainer = document.getElementById('summaryContainer')
|
|
61
|
+
const box = document.createElement('div');
|
|
62
|
+
box.className = 'summaryBox running';
|
|
63
|
+
summaryContainer.appendChild(box);
|
|
64
|
+
return box;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function updateSummaryBox(box, passed) {
|
|
68
|
+
box.className = `summaryBox ${passed ? 'passed' : passed === false ? 'failed' : 'other'}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function runTest(testName, testFunction, expectedResult) {
|
|
72
|
+
delay += 70
|
|
73
|
+
|
|
74
|
+
await new Promise(done => setTimeout(done, delay))
|
|
75
|
+
const div = createTestDiv(testName)
|
|
76
|
+
const summaryBox = createSummaryBox()
|
|
77
|
+
try {
|
|
78
|
+
let x = await testFunction()
|
|
79
|
+
if (x == expectedResult) {
|
|
80
|
+
updateTestResult(div, true, x)
|
|
81
|
+
updateSummaryBox(summaryBox, true)
|
|
82
|
+
} else {
|
|
83
|
+
updateTestResult(div, false, "Mismatch:", x, expectedResult)
|
|
84
|
+
updateSummaryBox(summaryBox, false)
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
updateTestResult(div, false, "Error:", error.message || error, expectedResult)
|
|
88
|
+
updateSummaryBox(summaryBox, false)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
runTest(
|
|
93
|
+
"test that peer.txt gets initialized on a fresh run",
|
|
94
|
+
async () => {
|
|
95
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: `void (async () => {
|
|
98
|
+
var db_name = 'test-db-' + Math.random().toString(36).slice(2)
|
|
99
|
+
|
|
100
|
+
var new_bb = braid_blob.create_braid_blob()
|
|
101
|
+
new_bb.db_folder = __dirname + '/' + db_name
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
await new_bb.serve({}, {})
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
|
|
107
|
+
await require('fs').promises.rm(new_bb.db_folder,
|
|
108
|
+
{ recursive: true, force: true })
|
|
109
|
+
|
|
110
|
+
res.end(new_bb.peer)
|
|
111
|
+
|
|
112
|
+
})()`
|
|
113
|
+
})
|
|
114
|
+
return '' + ((await r1.text()).length > 5)
|
|
115
|
+
},
|
|
116
|
+
'true'
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
runTest(
|
|
120
|
+
"test that peer is same the second time we run from same db folder",
|
|
121
|
+
async () => {
|
|
122
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
body: `void (async () => {
|
|
125
|
+
var db = __dirname + '/test-db-' + Math.random().toString(36).slice(2)
|
|
126
|
+
|
|
127
|
+
var bb1 = braid_blob.create_braid_blob()
|
|
128
|
+
bb1.db_folder = db
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await bb1.serve({}, {})
|
|
132
|
+
} catch (e) {}
|
|
133
|
+
|
|
134
|
+
var bb2 = braid_blob.create_braid_blob()
|
|
135
|
+
bb2.db_folder = db
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await bb2.serve({}, {})
|
|
139
|
+
} catch (e) {}
|
|
140
|
+
|
|
141
|
+
await require('fs').promises.rm(db, { recursive: true, force: true })
|
|
142
|
+
|
|
143
|
+
res.end('' + (bb1.peer === bb2.peer))
|
|
144
|
+
})()`
|
|
145
|
+
})
|
|
146
|
+
return await r1.text()
|
|
147
|
+
},
|
|
148
|
+
'true'
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
runTest(
|
|
152
|
+
"test that we can set the peer of a braid_blob object",
|
|
153
|
+
async () => {
|
|
154
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
body: `void (async () => {
|
|
157
|
+
var db = __dirname + '/test-db-' + Math.random().toString(36).slice(2)
|
|
158
|
+
|
|
159
|
+
var bb1 = braid_blob.create_braid_blob()
|
|
160
|
+
bb1.db_folder = db
|
|
161
|
+
bb1.peer = 'test_peer'
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
await bb1.serve({}, {})
|
|
165
|
+
} catch (e) {}
|
|
166
|
+
|
|
167
|
+
await require('fs').promises.rm(db, { recursive: true, force: true })
|
|
168
|
+
|
|
169
|
+
res.end(bb1.peer)
|
|
170
|
+
})()`
|
|
171
|
+
})
|
|
172
|
+
return await r1.text()
|
|
173
|
+
},
|
|
174
|
+
'test_peer'
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
runTest(
|
|
178
|
+
"test that PUTing with shorter event id doesn't do anything.",
|
|
179
|
+
async () => {
|
|
180
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
181
|
+
|
|
182
|
+
var r = await braid_fetch(`/${key}`, {
|
|
183
|
+
method: 'PUT',
|
|
184
|
+
version: ['11'],
|
|
185
|
+
body: 'xyz'
|
|
186
|
+
})
|
|
187
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
188
|
+
|
|
189
|
+
var r = await braid_fetch(`/${key}`, {
|
|
190
|
+
method: 'PUT',
|
|
191
|
+
version: ['9'],
|
|
192
|
+
body: 'abc'
|
|
193
|
+
})
|
|
194
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
195
|
+
|
|
196
|
+
var r = await braid_fetch(`/${key}`)
|
|
197
|
+
return await r.text()
|
|
198
|
+
},
|
|
199
|
+
'xyz'
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
runTest(
|
|
203
|
+
"test that we ignore stuff after the ? in a url",
|
|
204
|
+
async () => {
|
|
205
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
206
|
+
|
|
207
|
+
var r = await braid_fetch(`/${key}?blah`, {
|
|
208
|
+
method: 'PUT',
|
|
209
|
+
version: ['11'],
|
|
210
|
+
body: 'yo!'
|
|
211
|
+
})
|
|
212
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
213
|
+
|
|
214
|
+
var r = await braid_fetch(`/${key}`)
|
|
215
|
+
return await r.text()
|
|
216
|
+
},
|
|
217
|
+
'yo!'
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
runTest(
|
|
221
|
+
"test that we ignore stuff after the # in a url",
|
|
222
|
+
async () => {
|
|
223
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
224
|
+
|
|
225
|
+
var r = await braid_fetch(`/${key}#blah?bloop`, {
|
|
226
|
+
method: 'PUT',
|
|
227
|
+
version: ['11'],
|
|
228
|
+
body: 'hi!'
|
|
229
|
+
})
|
|
230
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
231
|
+
|
|
232
|
+
var r = await braid_fetch(`/${key}`)
|
|
233
|
+
return await r.text()
|
|
234
|
+
},
|
|
235
|
+
'hi!'
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
runTest(
|
|
239
|
+
"test send an update to another peer",
|
|
240
|
+
async () => {
|
|
241
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
242
|
+
|
|
243
|
+
var r = await braid_fetch(`/${key}`, {
|
|
244
|
+
method: 'PUT',
|
|
245
|
+
headers: {'Content-Type': 'text/plain'},
|
|
246
|
+
version: ['1'],
|
|
247
|
+
body: 'xyz'
|
|
248
|
+
})
|
|
249
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
250
|
+
|
|
251
|
+
var a = new AbortController()
|
|
252
|
+
var r = await braid_fetch(`/${key}`, {
|
|
253
|
+
signal: a.signal,
|
|
254
|
+
subscribe: true,
|
|
255
|
+
peer: key
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
var p = new Promise(done => {
|
|
259
|
+
r.subscribe(update => {
|
|
260
|
+
if (update.version?.[0] !== '2') return
|
|
261
|
+
done(update.body_text)
|
|
262
|
+
a.abort()
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
var r = await braid_fetch(`/${key}`, {
|
|
267
|
+
method: 'PUT',
|
|
268
|
+
headers: {'Content-Type': 'text/plain'},
|
|
269
|
+
version: ['2'],
|
|
270
|
+
body: 'abc'
|
|
271
|
+
})
|
|
272
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
273
|
+
|
|
274
|
+
return await p
|
|
275
|
+
},
|
|
276
|
+
'abc'
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
runTest(
|
|
280
|
+
"test having multiple subs",
|
|
281
|
+
async () => {
|
|
282
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
283
|
+
var key2 = 'test2-' + Math.random().toString(36).slice(2)
|
|
284
|
+
|
|
285
|
+
var r = await braid_fetch(`/${key}`, {
|
|
286
|
+
method: 'PUT',
|
|
287
|
+
headers: {'Content-Type': 'text/plain'},
|
|
288
|
+
version: ['1'],
|
|
289
|
+
body: 'xyz'
|
|
290
|
+
})
|
|
291
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
292
|
+
|
|
293
|
+
var a = new AbortController()
|
|
294
|
+
var r = await braid_fetch(`/${key}`, {
|
|
295
|
+
signal: a.signal,
|
|
296
|
+
subscribe: true,
|
|
297
|
+
peer: key
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
var p = new Promise(done => {
|
|
301
|
+
r.subscribe(update => {
|
|
302
|
+
if (update.version?.[0] !== '2') return
|
|
303
|
+
done(update.body_text)
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
var r = await braid_fetch(`/${key2}`, {
|
|
308
|
+
method: 'PUT',
|
|
309
|
+
headers: {'Content-Type': 'text/plain'},
|
|
310
|
+
version: ['1'],
|
|
311
|
+
body: 'xyz2'
|
|
312
|
+
})
|
|
313
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
314
|
+
|
|
315
|
+
var a = new AbortController()
|
|
316
|
+
var r = await braid_fetch(`/${key2}`, {
|
|
317
|
+
signal: a.signal,
|
|
318
|
+
subscribe: true,
|
|
319
|
+
peer: key2
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
var p2 = new Promise(done => {
|
|
323
|
+
r.subscribe(update => {
|
|
324
|
+
if (update.version?.[0] !== '2') return
|
|
325
|
+
done(update.body_text)
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
var r = await braid_fetch(`/${key}`, {
|
|
330
|
+
method: 'PUT',
|
|
331
|
+
headers: {'Content-Type': 'text/plain'},
|
|
332
|
+
version: ['2'],
|
|
333
|
+
body: 'abc'
|
|
334
|
+
})
|
|
335
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
336
|
+
|
|
337
|
+
var r = await braid_fetch(`/${key2}`, {
|
|
338
|
+
method: 'PUT',
|
|
339
|
+
headers: {'Content-Type': 'text/plain'},
|
|
340
|
+
version: ['2'],
|
|
341
|
+
body: 'abc2'
|
|
342
|
+
})
|
|
343
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
344
|
+
|
|
345
|
+
var ret = await Promise.all([p, p2])
|
|
346
|
+
a.abort()
|
|
347
|
+
return 'got: ' + ret
|
|
348
|
+
|
|
349
|
+
},
|
|
350
|
+
'got: abc,abc2'
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
runTest(
|
|
354
|
+
"test getting a 406",
|
|
355
|
+
async () => {
|
|
356
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
357
|
+
|
|
358
|
+
var r = await braid_fetch(`/${key}`, {
|
|
359
|
+
method: 'PUT',
|
|
360
|
+
headers: {'Content-Type': 'text/plain'},
|
|
361
|
+
version: ['1'],
|
|
362
|
+
parents: [],
|
|
363
|
+
body: 'xyz'
|
|
364
|
+
})
|
|
365
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
366
|
+
|
|
367
|
+
var r = await braid_fetch(`/${key}`, {
|
|
368
|
+
headers: {Accept: 'text/html'}
|
|
369
|
+
})
|
|
370
|
+
return r.status + ' ' + await r.text()
|
|
371
|
+
},
|
|
372
|
+
'406 Content-Type of text/plain not in Accept: text/html'
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
runTest(
|
|
376
|
+
"test deleting something",
|
|
377
|
+
async () => {
|
|
378
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
379
|
+
|
|
380
|
+
var r = await braid_fetch(`/${key}`, {
|
|
381
|
+
method: 'PUT',
|
|
382
|
+
version: ['1'],
|
|
383
|
+
parents: [],
|
|
384
|
+
body: 'xyz'
|
|
385
|
+
})
|
|
386
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
387
|
+
|
|
388
|
+
var r = await braid_fetch(`/${key}`, {
|
|
389
|
+
method: 'DELETE',
|
|
390
|
+
})
|
|
391
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
392
|
+
|
|
393
|
+
var r = await braid_fetch(`/${key}`)
|
|
394
|
+
return r.status
|
|
395
|
+
},
|
|
396
|
+
'404'
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
runTest(
|
|
400
|
+
"test deleting something that doesn't exist",
|
|
401
|
+
async () => {
|
|
402
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
403
|
+
|
|
404
|
+
var r = await braid_fetch(`/${key}`, {
|
|
405
|
+
method: 'DELETE',
|
|
406
|
+
})
|
|
407
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
408
|
+
|
|
409
|
+
return r.status
|
|
410
|
+
},
|
|
411
|
+
'204'
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
runTest(
|
|
415
|
+
"test that subscribe returns current-version header",
|
|
416
|
+
async () => {
|
|
417
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
418
|
+
|
|
419
|
+
var r = await braid_fetch(`/${key}`, {
|
|
420
|
+
method: 'PUT',
|
|
421
|
+
version: ['1'],
|
|
422
|
+
parents: [],
|
|
423
|
+
body: 'xyz'
|
|
424
|
+
})
|
|
425
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
426
|
+
|
|
427
|
+
var a = new AbortController()
|
|
428
|
+
var r = await braid_fetch(`/${key}`, {
|
|
429
|
+
signal: a.signal,
|
|
430
|
+
subscribe: true
|
|
431
|
+
})
|
|
432
|
+
a.abort()
|
|
433
|
+
return r.headers.get('current-version')
|
|
434
|
+
},
|
|
435
|
+
'"1"'
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
runTest(
|
|
439
|
+
"test that subscribe returns version as string-number in array",
|
|
440
|
+
async () => {
|
|
441
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
442
|
+
|
|
443
|
+
var r = await braid_fetch(`/${key}`, {
|
|
444
|
+
method: 'PUT',
|
|
445
|
+
version: ['1'],
|
|
446
|
+
parents: [],
|
|
447
|
+
body: 'xyz'
|
|
448
|
+
})
|
|
449
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
450
|
+
|
|
451
|
+
var a = new AbortController()
|
|
452
|
+
var r = await braid_fetch(`/${key}`, {
|
|
453
|
+
signal: a.signal,
|
|
454
|
+
subscribe: true
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
var x = await new Promise(done => {
|
|
458
|
+
r.subscribe(update => {
|
|
459
|
+
done(update.version)
|
|
460
|
+
})
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
a.abort()
|
|
464
|
+
return JSON.stringify(x)
|
|
465
|
+
},
|
|
466
|
+
'["1"]'
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
runTest(
|
|
470
|
+
"test that subscribe's update's versions are string-number in array",
|
|
471
|
+
async () => {
|
|
472
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
473
|
+
|
|
474
|
+
var r = await braid_fetch(`/${key}`, {
|
|
475
|
+
method: 'PUT',
|
|
476
|
+
version: ['4'],
|
|
477
|
+
body: 'xyz'
|
|
478
|
+
})
|
|
479
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
480
|
+
|
|
481
|
+
var a = new AbortController()
|
|
482
|
+
var r = await braid_fetch(`/${key}`, {
|
|
483
|
+
signal: a.signal,
|
|
484
|
+
subscribe: true,
|
|
485
|
+
parents: ['0']
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
var p = new Promise(done => {
|
|
489
|
+
r.subscribe(update => {
|
|
490
|
+
done(update.version)
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
var ret = await p
|
|
495
|
+
a.abort()
|
|
496
|
+
return JSON.stringify(ret)
|
|
497
|
+
},
|
|
498
|
+
'["4"]'
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
runTest(
|
|
502
|
+
"test that non-subscribe returns version header",
|
|
503
|
+
async () => {
|
|
504
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
505
|
+
|
|
506
|
+
var r = await braid_fetch(`/${key}`, {
|
|
507
|
+
method: 'PUT',
|
|
508
|
+
version: ['2'],
|
|
509
|
+
parents: [],
|
|
510
|
+
body: 'xyz'
|
|
511
|
+
})
|
|
512
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
513
|
+
|
|
514
|
+
var r = await braid_fetch(`/${key}`)
|
|
515
|
+
return r.headers.get('version')
|
|
516
|
+
},
|
|
517
|
+
'"2"'
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
runTest(
|
|
521
|
+
"test that PUTing at version [] doesn't do anything.",
|
|
522
|
+
async () => {
|
|
523
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
524
|
+
|
|
525
|
+
var r = await braid_fetch(`/${key}`, {
|
|
526
|
+
method: 'PUT',
|
|
527
|
+
version: [],
|
|
528
|
+
parents: [],
|
|
529
|
+
body: 'xyz'
|
|
530
|
+
})
|
|
531
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
532
|
+
|
|
533
|
+
var r = await braid_fetch(`/${key}`)
|
|
534
|
+
return r.status
|
|
535
|
+
},
|
|
536
|
+
'404'
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
runTest(
|
|
540
|
+
"test that subscribe sends no version if parents is big enough.",
|
|
541
|
+
async () => {
|
|
542
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
543
|
+
|
|
544
|
+
var r = await braid_fetch(`/${key}`, {
|
|
545
|
+
method: 'PUT',
|
|
546
|
+
version: ['3'],
|
|
547
|
+
parents: [],
|
|
548
|
+
body: 'xyz'
|
|
549
|
+
})
|
|
550
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
551
|
+
|
|
552
|
+
var a = new AbortController()
|
|
553
|
+
var r = await braid_fetch(`/${key}`, {
|
|
554
|
+
signal: a.signal,
|
|
555
|
+
subscribe: true,
|
|
556
|
+
parents: ['3']
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
var received_update = false
|
|
560
|
+
var promise_a = new Promise(done => {
|
|
561
|
+
r.subscribe(async (update) => {
|
|
562
|
+
received_update = true
|
|
563
|
+
done()
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
var promise_b = new Promise(done => setTimeout(done, 300))
|
|
568
|
+
|
|
569
|
+
await Promise.race([promise_a, promise_b])
|
|
570
|
+
a.abort()
|
|
571
|
+
|
|
572
|
+
return '' + received_update
|
|
573
|
+
},
|
|
574
|
+
'false'
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
runTest(
|
|
578
|
+
"test that subscribe sends 404 if there is no file.",
|
|
579
|
+
async () => {
|
|
580
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
581
|
+
|
|
582
|
+
var r = await braid_fetch(`/${key}`, {
|
|
583
|
+
subscribe: true,
|
|
584
|
+
})
|
|
585
|
+
return r.status
|
|
586
|
+
},
|
|
587
|
+
'404'
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
runTest(
|
|
591
|
+
"test that we get 404 when file doesn't exist, on GET without subscribe.",
|
|
592
|
+
async () => {
|
|
593
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
594
|
+
|
|
595
|
+
var r = await braid_fetch(`/${key}`)
|
|
596
|
+
|
|
597
|
+
return `${r.status}`
|
|
598
|
+
},
|
|
599
|
+
'404'
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
runTest(
|
|
603
|
+
"test second subscription to same key",
|
|
604
|
+
async () => {
|
|
605
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
606
|
+
|
|
607
|
+
var r = await braid_fetch(`/${key}`, {
|
|
608
|
+
method: 'PUT',
|
|
609
|
+
version: ['3'],
|
|
610
|
+
parents: [],
|
|
611
|
+
body: 'xyz'
|
|
612
|
+
})
|
|
613
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
614
|
+
|
|
615
|
+
var a = new AbortController()
|
|
616
|
+
var r = await braid_fetch(`/${key}`, {
|
|
617
|
+
signal: a.signal,
|
|
618
|
+
subscribe: true,
|
|
619
|
+
parents: ['3']
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
var a2 = new AbortController()
|
|
623
|
+
var r2 = await braid_fetch(`/${key}`, {
|
|
624
|
+
signal: a2.signal,
|
|
625
|
+
subscribe: true,
|
|
626
|
+
parents: ['2']
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
var body = await new Promise(done => {
|
|
630
|
+
r2.subscribe((update) => done(update.body_text))
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
a.abort()
|
|
634
|
+
a2.abort()
|
|
635
|
+
return body
|
|
636
|
+
},
|
|
637
|
+
'xyz'
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
runTest(
|
|
641
|
+
"test PUTing when server already has blob",
|
|
642
|
+
async () => {
|
|
643
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
644
|
+
|
|
645
|
+
var r = await braid_fetch(`/${key}`, {
|
|
646
|
+
method: 'PUT',
|
|
647
|
+
version: ['3'],
|
|
648
|
+
parents: [],
|
|
649
|
+
body: 'xyz'
|
|
650
|
+
})
|
|
651
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
652
|
+
|
|
653
|
+
var r = await braid_fetch(`/${key}`, {
|
|
654
|
+
method: 'PUT',
|
|
655
|
+
version: ['4'],
|
|
656
|
+
parents: [],
|
|
657
|
+
body: 'XYZ'
|
|
658
|
+
})
|
|
659
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
660
|
+
|
|
661
|
+
return await (await braid_fetch(`/${key}`)).text()
|
|
662
|
+
},
|
|
663
|
+
'XYZ'
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
runTest(
|
|
667
|
+
"test PUTing when server has newer version",
|
|
668
|
+
async () => {
|
|
669
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
670
|
+
|
|
671
|
+
var r = await braid_fetch(`/${key}`, {
|
|
672
|
+
method: 'PUT',
|
|
673
|
+
version: ['3'],
|
|
674
|
+
parents: [],
|
|
675
|
+
body: 'xyz'
|
|
676
|
+
})
|
|
677
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
678
|
+
|
|
679
|
+
var r = await braid_fetch(`/${key}`, {
|
|
680
|
+
method: 'PUT',
|
|
681
|
+
version: ['2'],
|
|
682
|
+
parents: [],
|
|
683
|
+
body: 'XYZ'
|
|
684
|
+
})
|
|
685
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
686
|
+
|
|
687
|
+
return r.headers.get('version')
|
|
688
|
+
},
|
|
689
|
+
'"3"'
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
runTest(
|
|
693
|
+
"test that version we get back is the version we set",
|
|
694
|
+
async () => {
|
|
695
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
696
|
+
|
|
697
|
+
var r = await braid_fetch(`/${key}`, {
|
|
698
|
+
method: 'PUT',
|
|
699
|
+
version: ['1760077018883'],
|
|
700
|
+
parents: [],
|
|
701
|
+
body: 'xyz'
|
|
702
|
+
})
|
|
703
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
704
|
+
|
|
705
|
+
var r = await braid_fetch(`/${key}`)
|
|
706
|
+
return r.headers.get('version')
|
|
707
|
+
},
|
|
708
|
+
'"1760077018883"'
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
runTest(
|
|
712
|
+
"test that subscribe gets back editable:true.",
|
|
713
|
+
async () => {
|
|
714
|
+
var key = 'test-' + Math.random().toString(36).slice(2)
|
|
715
|
+
|
|
716
|
+
var r = await braid_fetch(`/${key}`, {
|
|
717
|
+
method: 'PUT',
|
|
718
|
+
body: 'xyz'
|
|
719
|
+
})
|
|
720
|
+
if (!r.ok) throw 'got: ' + r.statusCode
|
|
721
|
+
|
|
722
|
+
var a = new AbortController()
|
|
723
|
+
var r = await braid_fetch(`/${key}`, {
|
|
724
|
+
signal: a.signal,
|
|
725
|
+
subscribe: true,
|
|
726
|
+
parents: ['3']
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
var ret = r.headers.get('editable')
|
|
730
|
+
a.abort()
|
|
731
|
+
return '' + ret
|
|
732
|
+
},
|
|
733
|
+
'true'
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
runTest(
|
|
737
|
+
"test that we can override editable on the server.",
|
|
738
|
+
async () => {
|
|
739
|
+
var r1 = await braid_fetch(`/eval`, {
|
|
740
|
+
method: 'POST',
|
|
741
|
+
subscribe: true,
|
|
742
|
+
body: `void (async () => {
|
|
743
|
+
req.method = 'GET'
|
|
744
|
+
res.setHeader('editable', 'false')
|
|
745
|
+
braid_blob.serve(req, res, {key: ':test'})
|
|
746
|
+
})()`
|
|
747
|
+
})
|
|
748
|
+
return r1.headers.get('editable')
|
|
749
|
+
|
|
750
|
+
},
|
|
751
|
+
'false'
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
</script>
|