Files: d185716fb11842271c37ff499725b2b66ba2a5e3 / lib / util.js
8838 bytesRaw
1 | var pull = require('pull-stream') |
2 | var cat = require('pull-cat') |
3 | var h = require('hyperscript') |
4 | var b64url = require('base64-url') |
5 | var u = exports |
6 | |
7 | u.ssbRefRegex = /((?:@|%|&|ssb:\/\/%)[A-Za-z0-9\/+]{43}=\.[\w\d]+)/g |
8 | u.ssbRefRegexOnly = /^(?:@|%|&|ssb:\/\/%)[A-Za-z0-9\/+]{43}=\.[\w\d]+$/ |
9 | u.ssbRefEncRegex = /((?:ssb:\/\/)?(?:[@%&]|%26|%40|%25)(?:[A-Za-z0-9\/+]|%2[fF]|%2[bB]){43}(?:=|%3[dD])\.[\w\d]+)/g |
10 | u.symbolRegex = /([^\u0000-\u00ff]+)/g |
11 | u.channelRegex = /^#[^\s#]+$/ |
12 | u.ssbURIRegex = /^(ssb:(message|blob|feed)([:\/])([^:?]*?)([:/])([^?]*)(\\?.*)?)$/ |
13 | u.gitSsbRepoIdRegex = /^ssb:\/\/(%[A-Za-z0-9\/+]{43}=\.sha256)(\?.*)?$/ |
14 | |
15 | u.isRef = function (str) { |
16 | if (!str) return false |
17 | u.ssbRefRegexOnly.lastIndex = 0 |
18 | return u.ssbRefRegexOnly.test(str) |
19 | } |
20 | |
21 | u.isChannel = function (value) { |
22 | return typeof value === 'string' |
23 | && value.length < 30 |
24 | && u.channelRegex.test(value) |
25 | } |
26 | |
27 | u.readNext = function (fn) { |
28 | var next |
29 | return function (end, cb) { |
30 | if (next) return next(end, cb) |
31 | fn(function (err, _next) { |
32 | if (err) return cb(err) |
33 | next = _next |
34 | next(null, cb) |
35 | }) |
36 | } |
37 | } |
38 | |
39 | u.pullReverse = function () { |
40 | return function (read) { |
41 | return u.readNext(function (cb) { |
42 | pull(read, pull.collect(function (err, items) { |
43 | cb(err, items && pull.values(items.reverse())) |
44 | })) |
45 | }) |
46 | } |
47 | } |
48 | |
49 | u.toHTML = function (el) { |
50 | if (!el) return '' |
51 | if (typeof el === 'string' || Array.isArray(el)) { |
52 | return h('div', el).innerHTML |
53 | } |
54 | var html = el.outerHTML || String(el) |
55 | if (el.nodeName === 'html') html = '<!doctype html>' + html + '\n' |
56 | return html |
57 | } |
58 | |
59 | u.hyperwrap = function (fn) { |
60 | var token = '__HYPERWRAP_' + Math.random() + '__' |
61 | return function (read) { |
62 | return u.readNext(function (cb) { |
63 | fn(token, function (err, el) { |
64 | if (err) return cb(err) |
65 | var parts = u.toHTML(el).split(token) |
66 | switch (parts.length) { |
67 | case 0: return cb(null, pull.empty()) |
68 | case 1: return cb(null, pull.once(parts[0])) |
69 | case 2: return cb(null, |
70 | cat([pull.once(parts[0]), read, pull.once(parts[1])])) |
71 | default: return cb(new Error('duplicate wrap')) |
72 | } |
73 | }) |
74 | }) |
75 | } |
76 | } |
77 | |
78 | u.ifString = function (str) { |
79 | if (typeof str === 'string') return str |
80 | } |
81 | |
82 | u.ifNumber = function (num) { |
83 | if (!isNaN(num)) return num |
84 | } |
85 | |
86 | u.toLink = function (link) { |
87 | return typeof link === 'string' ? {link: link} : link || null |
88 | } |
89 | |
90 | u.linkDest = function (link) { |
91 | return link && (u.ifString(link) || u.ifString(link.link)) |
92 | } |
93 | |
94 | u.toArray = function (x) { |
95 | return x == null ? [] : Array.isArray(x) ? x : [x] |
96 | } |
97 | |
98 | u.fromArray = function (arr) { |
99 | return Array.isArray(arr) && arr.length === 1 ? arr[0] : arr |
100 | } |
101 | |
102 | u.toLinkArray = function (x) { |
103 | return u.toArray(x).map(u.toLink).filter(u.linkDest) |
104 | } |
105 | |
106 | u.toString = function (str) { |
107 | switch (typeof str) { |
108 | case 'string': return str |
109 | case 'object': |
110 | if (str !== null) return JSON.stringify(str) |
111 | default: |
112 | return String(str) |
113 | } |
114 | } |
115 | |
116 | u.renderError = function(err) { |
117 | return h('div.error', |
118 | h('h3', err.name), |
119 | h('pre', err.stack)) |
120 | } |
121 | |
122 | u.pullLength = function (cb) { |
123 | var len = 0 |
124 | return pull.through(function (data) { |
125 | len += data.length |
126 | }, function (err) { |
127 | cb(err, len) |
128 | }) |
129 | } |
130 | |
131 | u.tryDecodeJSON = function (json) { |
132 | try { |
133 | return JSON.parse(json) |
134 | } catch(e) { |
135 | return null |
136 | } |
137 | } |
138 | |
139 | u.extractRefs = function (str) { |
140 | var ids = [] |
141 | String(str).replace(u.ssbRefRegex, function (id) { |
142 | ids.push(id) |
143 | }) |
144 | return ids |
145 | } |
146 | |
147 | u.extractFeedIds = function (str) { |
148 | return u.extractRefs(str).filter(function (ref) { |
149 | return ref[0] === '@' |
150 | }) |
151 | } |
152 | |
153 | u.extractBlobIds = function (str) { |
154 | return u.extractRefs(str).filter(function (ref) { |
155 | return ref[0] === '&' |
156 | }) |
157 | } |
158 | |
159 | u.isMsgReadable = function (msg) { |
160 | var c = msg && msg.value && msg.value.content |
161 | return typeof c === 'object' && c !== null |
162 | } |
163 | |
164 | u.isMsgEncrypted = function (msg) { |
165 | var value = msg && msg.value |
166 | return value && (value.private || typeof value.content === 'string') |
167 | } |
168 | |
169 | u.pullConcat = function (cb) { |
170 | return pull.collect(function (err, bufs) { |
171 | if (err) return cb(err) |
172 | cb(null, Buffer.concat(bufs)) |
173 | }) |
174 | } |
175 | |
176 | u.customError = function (name) { |
177 | return function (message) { |
178 | var error = new Error(message) |
179 | error.name = name |
180 | error.stack = error.stack.replace(/^ at .*\n/m, '') |
181 | return error |
182 | } |
183 | } |
184 | |
185 | u.escapeHTML = function (html) { |
186 | if (!html) return '' |
187 | return html.toString('utf8') |
188 | .replace(/&/g, '&') |
189 | .replace(/"/g, '"') |
190 | .replace(/</g, '<') |
191 | .replace(/>/g, '>') |
192 | .replace(/\n/g, '
') |
193 | } |
194 | |
195 | u.unescapeHTML = function (text) { |
196 | if (typeof text !== 'string') return text |
197 | return text.replace(/&/g, '&') |
198 | .replace(/"/g, '"') |
199 | .replace(/'/g, '\'') |
200 | .replace(/>/g, '>') |
201 | .replace(/</g, '<') |
202 | } |
203 | |
204 | u.pullSlice = function (start, end) { |
205 | if (end == null) end = Infinity |
206 | var offset = 0 |
207 | return function (read) { |
208 | return function (abort, cb) { |
209 | if (abort) read(abort, cb) |
210 | else if (offset >= end) read(true, function (err) { |
211 | cb(err || true) |
212 | }) |
213 | else if (offset < start) read(null, function next(err, data) { |
214 | if (err) return cb(err) |
215 | offset += data.length |
216 | if (offset <= start) read(null, next) |
217 | else if (offset < end) cb(null, |
218 | data.slice(data.length - (offset - start))) |
219 | else cb(null, data.slice(data.length - (offset - start), |
220 | data.length - (offset - end))) |
221 | }) |
222 | else read(null, function (err, data) { |
223 | if (err) return cb(err) |
224 | offset += data.length |
225 | if (offset <= end) cb(null, data) |
226 | else cb(null, data.slice(0, data.length - (offset - end))) |
227 | }) |
228 | } |
229 | } |
230 | } |
231 | |
232 | u.unboxSize = function (size) { |
233 | // Transform size of boxed blob into size of cleartext. |
234 | // Assume that the blob contains box-stream packets that are all full 4096-byte packets except maybe the last one, plus one goodbye packet |
235 | // boxedlen = origlen + ceil(origlen / 4096)*34 + 34 |
236 | const numBoxHeaders = Math.ceil((size - 34) / 4130) + 1 |
237 | return size - numBoxHeaders * 34 |
238 | } |
239 | |
240 | u.mergeOpts = function (a, b) { |
241 | if (typeof b !== 'object' || b === null) return b |
242 | if (typeof a !== 'object' || a === null) return a |
243 | var obj = {}, k |
244 | for (k in a) obj[k] = k in b ? u.mergeOpts(a[k], b[k]) : a[k] |
245 | for (k in b) if (!(k in a) && b[k] != null) obj[k] = b[k] |
246 | return obj |
247 | } |
248 | |
249 | u.escapeId = function (id) { |
250 | return b64url.escape(id) |
251 | } |
252 | |
253 | u.unescapeId = function (str) { |
254 | var m = /^(.)(.*)(\..*)$/.exec(str) |
255 | if (!m) return b64url.unescape(str) |
256 | return m[1] + b64url.unescape(m[2]) + m[3] |
257 | } |
258 | |
259 | u.rows = function (str) { |
260 | return String(str).split(/[^\n]{150}|\n/).length |
261 | } |
262 | |
263 | u.token = function () { |
264 | return '__' + Math.random().toString(36).substr(2) + '__' |
265 | } |
266 | |
267 | u.unwrapP = function(html) { |
268 | return String(html).replace(/^<p>(.*)<\/p>\s*$/, function ($0, $1) { |
269 | return $1 |
270 | }) |
271 | } |
272 | |
273 | u.estimateMessageSize = function (content) { |
274 | var draftValue = { |
275 | previous: '%0000000000000000000000000000000000000000000=.sha256', |
276 | author: '@0000000000000000000000000000000000000000000=.ed25519', |
277 | sequence: 10000, |
278 | timestamp: 1000000000000, |
279 | hash: 'sha256', |
280 | content: content, |
281 | signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==.sig.ed25519' |
282 | } |
283 | |
284 | return JSON.stringify(draftValue, null, 2).length |
285 | } |
286 | |
287 | u.truncate = function (str, len) { |
288 | str = String(str) |
289 | return str.length > len ? str.substr(0, len) + '...' : str |
290 | } |
291 | |
292 | u.extractHostPort = function (id, addr) { |
293 | var i = addr.lastIndexOf(':' + id) |
294 | if (i > -1 && addr.length === i + id.length + 1) { |
295 | return addr.substr(0, i) |
296 | } |
297 | var m = /^(?:net|onion):([^~;]*(?::[0-9]*)?)~shs:([0-9a-zA-Z/=+]{44})$/.exec(addr) |
298 | if (m && id === '@' + m[2] + '.ed25519') { |
299 | return m[1] |
300 | } |
301 | return addr |
302 | } |
303 | |
304 | u.translateFromGitSsbRepoId = function (url) { |
305 | var m = u.gitSsbRepoIdRegex.exec(url) |
306 | if (!m) return |
307 | var id = m[1] |
308 | var qs = m[2] || '' |
309 | return id + qs |
310 | } |
311 | |
312 | u.translateFromURI = function (url) { |
313 | var m = u.ssbURIRegex.exec(url) |
314 | if (!m) return u.translateFromGitSsbRepoId(url) |
315 | var uri = m[1] |
316 | var prefix = m[2] |
317 | var separator = m[3] |
318 | var format = m[4] |
319 | var separator2 = m[5] |
320 | var hash = m[6] |
321 | var qs = m[7] || '' |
322 | if (separator !== separator2) return |
323 | hash = b64url.unescape(hash) |
324 | if (separator === '/') { |
325 | // ssb-custom-uri |
326 | try { hash = decodeURIComponent(hash) } |
327 | catch(e) {} |
328 | } |
329 | var sigil |
330 | switch (prefix) { |
331 | case 'message': sigil = '%'; break |
332 | case 'blob': sigil = '&'; break |
333 | case 'feed': sigil = '@'; break |
334 | default: return |
335 | } |
336 | return sigil + hash + '.' + format + qs |
337 | } |
338 | |
339 | u.isNotFoundError = function (err) { |
340 | return err && err.name == 'NotFoundError' |
341 | || (typeof err.message === 'string' |
342 | && err.message.startsWith('Key not found in database')) |
343 | } |
344 |
Built with git-ssb-web