turtb 0.5.0

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 (70) hide show
  1. package/LICENSE +661 -0
  2. package/bin/turtb.js +259 -0
  3. package/lib/display/h.js +204 -0
  4. package/lib/display/helpers.js +78 -0
  5. package/lib/display/render.js +177 -0
  6. package/lib/display/shapes.js +91 -0
  7. package/lib/turtle/Signer.js +120 -0
  8. package/lib/turtle/Signer.test.js +38 -0
  9. package/lib/turtle/TurtleBranch.js +147 -0
  10. package/lib/turtle/TurtleBranch.test.js +89 -0
  11. package/lib/turtle/TurtleDictionary.js +157 -0
  12. package/lib/turtle/TurtleDictionary.test.js +331 -0
  13. package/lib/turtle/U8aTurtle.js +203 -0
  14. package/lib/turtle/U8aTurtle.test.js +60 -0
  15. package/lib/turtle/Workspace.js +62 -0
  16. package/lib/turtle/Workspace.test.js +63 -0
  17. package/lib/turtle/codecs/CodecType.js +36 -0
  18. package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
  19. package/lib/turtle/codecs/Commit.js +10 -0
  20. package/lib/turtle/codecs/CompositeCodec.js +86 -0
  21. package/lib/turtle/codecs/TreeNode.js +38 -0
  22. package/lib/turtle/codecs/codec.js +441 -0
  23. package/lib/turtle/connections/AbstractUpdater.js +176 -0
  24. package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
  25. package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
  26. package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
  27. package/lib/turtle/connections/TurtleDB.js +165 -0
  28. package/lib/turtle/connections/TurtleDB.test.js +45 -0
  29. package/lib/turtle/connections/TurtleTalker.js +34 -0
  30. package/lib/turtle/connections/TurtleTalker.test.js +101 -0
  31. package/lib/turtle/utils.js +192 -0
  32. package/lib/turtle/utils.test.js +158 -0
  33. package/lib/utils/Assert.js +115 -0
  34. package/lib/utils/NestedSet.js +68 -0
  35. package/lib/utils/NestedSet.test.js +30 -0
  36. package/lib/utils/OWN_KEYS.js +1 -0
  37. package/lib/utils/Recaller.js +175 -0
  38. package/lib/utils/Recaller.test.js +75 -0
  39. package/lib/utils/TestRunner.js +200 -0
  40. package/lib/utils/TestRunner.test.js +144 -0
  41. package/lib/utils/TestRunnerConstants.js +13 -0
  42. package/lib/utils/combineUint8ArrayLikes.js +18 -0
  43. package/lib/utils/combineUint8Arrays.js +23 -0
  44. package/lib/utils/components.js +88 -0
  45. package/lib/utils/crypto.js +17 -0
  46. package/lib/utils/deepEqual.js +16 -0
  47. package/lib/utils/deepEqual.test.js +27 -0
  48. package/lib/utils/fileTransformer.js +16 -0
  49. package/lib/utils/handleRedirect.js +93 -0
  50. package/lib/utils/logger.js +24 -0
  51. package/lib/utils/nextTick.js +47 -0
  52. package/lib/utils/noble-secp256k1.js +602 -0
  53. package/lib/utils/proxyWithRecaller.js +51 -0
  54. package/lib/utils/toCombinedVersion.js +14 -0
  55. package/lib/utils/toSubVersions.js +14 -0
  56. package/lib/utils/toVersionCount.js +5 -0
  57. package/lib/utils/webSocketMuxFactory.js +123 -0
  58. package/lib/utils/zabacaba.js +25 -0
  59. package/package.json +24 -0
  60. package/src/ArchiveUpdater.js +99 -0
  61. package/src/S3Updater.js +99 -0
  62. package/src/archiveSync.js +28 -0
  63. package/src/fileSync.js +155 -0
  64. package/src/getExistenceLength.js +19 -0
  65. package/src/manageCert.js +36 -0
  66. package/src/originSync.js +75 -0
  67. package/src/outletSync.js +50 -0
  68. package/src/proxyFolder.js +195 -0
  69. package/src/s3Sync.js +32 -0
  70. package/src/webSync.js +101 -0
@@ -0,0 +1,88 @@
1
+ import { b64ToUi8 } from '../dataModel/Committer.js'
2
+ import { h } from '../display/h.js'
3
+ import { getPointerByPublicKey } from '../net/Peer.js'
4
+
5
+ export const getCpkSlice = cpk => b64ToUi8(cpk).slice(0, 6).map(n => `0${n.toString(16)}`.slice(-2)).join('')
6
+ export const getBase = relativePath => relativePath.match(/(?<base>[^/]*)\.[^/]*$/)?.groups?.base
7
+
8
+ export const buildElementName = (relativePath, address, cpk) => {
9
+ const base = getBase(relativePath)
10
+ const cpkSlice = getCpkSlice(cpk)
11
+ return `${base}-${cpkSlice}-${address}`
12
+ }
13
+
14
+ export const componentAtPath = (relativePath, cpk, baseElement) => {
15
+ const componentsByKey = {}
16
+ return (attributes = {}, children = []) => {
17
+ if (typeof cpk === 'function') cpk = cpk()
18
+ const elementName = componentNameAtPath(relativePath, cpk)
19
+ // logTrace(() => console.log('componentAtPath', { relativePath, cpk, elementName }))
20
+ if (elementName) {
21
+ const { key } = attributes
22
+ if (key && componentsByKey[key]) return componentsByKey[key]
23
+ let component
24
+ if (baseElement) {
25
+ component = h`<${baseElement} is=${elementName} ${attributes}>${children}</>`
26
+ } else {
27
+ component = h`<${elementName} ${attributes}>${children}</>`
28
+ }
29
+ if (key) {
30
+ componentsByKey[key] = component
31
+ }
32
+ return component
33
+ }
34
+ return 'loading...'
35
+ }
36
+ }
37
+
38
+ export const componentNameAtPath = (relativePath, cpk) => {
39
+ const turtle = getPointerByPublicKey(cpk)
40
+ const fsRefs = turtle.getRefs(turtle.getAddress(), 'value', 'fs')
41
+ if (!fsRefs) return
42
+ const address = fsRefs[relativePath]
43
+ if (address === undefined) return
44
+ const elementName = buildElementName(relativePath, address, cpk)
45
+ return elementName
46
+ }
47
+
48
+ /**
49
+ * @param {string} url
50
+ * @returns {{parsedURL: string, address: number, cpk: string, pointer: import('../dataModel/Uint8ArrayLayerPointer.js').Uint8ArrayLayerPointer, recaller: import('./Recaller.js').Recaller, elementName: string}}
51
+ **/
52
+ export const deriveDefaults = url => {
53
+ const parsedURL = new URL(url)
54
+ const address = parsedURL.searchParams.get('address')
55
+ const cpk = parsedURL.searchParams.get('cpk')
56
+ const pointer = getPointerByPublicKey(cpk)
57
+ const recaller = pointer.recaller
58
+ const elementName = buildElementName(parsedURL.pathname, address, cpk)
59
+ return { parsedURL, address, cpk, pointer, recaller, elementName }
60
+ }
61
+
62
+ export const parseLocation = () => ({ hash: window.location?.hash?.slice?.(1) })
63
+
64
+ const hashStateByRecaller = new Map()
65
+ export const useHash = recaller => {
66
+ if (!hashStateByRecaller.has(recaller)) {
67
+ let hash = ''
68
+ const setCpk = (newHash = '') => {
69
+ if (hash === newHash) return
70
+ hash = newHash
71
+ if (parseLocation().hash !== hash) {
72
+ window.history.pushState({}, '', `#${hash}`)
73
+ }
74
+ recaller.reportKeyMutation(recaller, 'hash', 'setCpk', 'window.location')
75
+ }
76
+ const getCpk = () => {
77
+ recaller.reportKeyAccess(recaller, 'hash', 'getCpk', 'window.location')
78
+ return parseLocation().hash
79
+ }
80
+ const updateHashState = () => {
81
+ setCpk(parseLocation().hash)
82
+ }
83
+ window.addEventListener('hashchange', () => updateHashState())
84
+ updateHashState()
85
+ hashStateByRecaller.set(recaller, { setCpk, getCpk, updateHashState })
86
+ }
87
+ return hashStateByRecaller.get(recaller)
88
+ }
@@ -0,0 +1,17 @@
1
+ export const cryptoPromise = (typeof crypto === 'undefined') ? import('crypto') : crypto
2
+
3
+ export const hashNameAndPassword = async (username, password, iterations = 100000) => {
4
+ const { subtle } = await cryptoPromise
5
+ const passwordKey = await subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits', 'deriveKey'])
6
+ const hashwordKey = await subtle.deriveKey(
7
+ { name: 'PBKDF2', salt: new TextEncoder().encode(username), iterations, hash: 'SHA-256' },
8
+ passwordKey,
9
+ { name: 'HMAC', hash: 'SHA-256' },
10
+ true,
11
+ ['sign']
12
+ )
13
+ return b2h(new Uint8Array(await subtle.exportKey('raw', hashwordKey)).slice(32))
14
+ }
15
+
16
+ const padh = (n, pad) => n.toString(16).padStart(pad, '0')
17
+ const b2h = (b) => Array.from(b).map(e => padh(e, 2)).join('') // bytes to hex
@@ -0,0 +1,16 @@
1
+ export function deepEqual (a, b) {
2
+ if (!a !== !b) return false
3
+ if (!a && !b && a === b) return true
4
+ const aType = typeof a
5
+ const bType = typeof b
6
+ if (aType !== bType) return false
7
+ if (aType !== 'object') return a === b
8
+ if (
9
+ !(a.constructor.name === 'Object' && b.constructor.name === 'Object') &&
10
+ (a.constructor !== b.constructor)
11
+ ) return false
12
+ const aKeys = Object.keys(a)
13
+ const bKeys = Object.keys(b)
14
+ if (aKeys.length !== bKeys.length) return false
15
+ return aKeys.every(key => deepEqual(a[key], b[key]))
16
+ }
@@ -0,0 +1,27 @@
1
+ import { deepEqual } from "./deepEqual.js";
2
+ import { globalTestRunner, urlToName } from "./TestRunner.js";
3
+
4
+ globalTestRunner.describe(urlToName(import.meta.url), suite => {
5
+ suite.it('can tell same things from different things', ({ assert }) => {
6
+ assert.assert(deepEqual(
7
+ new Uint8Array([1,2,3]),
8
+ new Uint8Array([1,2,3])
9
+ ))
10
+ assert.assert(!deepEqual(
11
+ new Uint8Array([1,2,3]),
12
+ new Uint16Array([1,2,3])
13
+ ))
14
+ assert.assert(!deepEqual( null, undefined))
15
+ assert.assert(deepEqual( null, null))
16
+ assert.assert(deepEqual( undefined, undefined))
17
+ assert.assert(deepEqual( 'asdf', 'asdf'))
18
+ assert.assert(deepEqual(
19
+ [new Uint8Array([1,2,3])],
20
+ [new Uint8Array([1,2,3])]
21
+ ))
22
+ assert.assert(deepEqual(
23
+ {a: 1, b: [new Uint8Array([1,2,3])]},
24
+ {a: 1, b: [new Uint8Array([1,2,3])]}
25
+ ))
26
+ })
27
+ })
@@ -0,0 +1,16 @@
1
+ export const textFileExtensions = ['js', 'html', 'css', 'txt', 'gitignore']
2
+ export const jsonFileExtensions = ['json']
3
+
4
+ export const TEXT_FILE = 'text file'
5
+ export const JSON_FILE = 'json file'
6
+ export const BINARY_FILE = 'binary file'
7
+
8
+ export const pathToType = path => {
9
+ const extension = path.split('.').pop()
10
+ if (textFileExtensions.includes(extension)) return TEXT_FILE
11
+ if (jsonFileExtensions.includes(extension)) return JSON_FILE
12
+ return BINARY_FILE
13
+ }
14
+
15
+ export const stringToLines = string => string.split('\n')
16
+ export const linesToString = lines => (typeof lines === 'string' && lines) || (Array.isArray(lines) && lines.join('\n')) || ''
@@ -0,0 +1,93 @@
1
+ import { logDebug, logError, logInfo } from './logger.js'
2
+
3
+ /**
4
+ * @typedef {import('../turtle/connections/TurtleDB.js').TurtleDB} TurtleDB
5
+ */
6
+
7
+ export const defaultPublicKey = 'ctclduqytfepmxfpxe8561b8h75l4u5n2t3sxlrmfc889xjz57'
8
+
9
+ /**
10
+ *
11
+ * @param {URL} url
12
+ * @param {string} address
13
+ * @param {TurtleDB} turtleDB
14
+ * @param {string} publicKey
15
+ * @param {string} turtleDBFolder
16
+ * @param {(href: string) => void} redirect
17
+ * @param {(type: string, body: string) => void} reply
18
+ */
19
+ export const handleRedirect = async (url, address, turtleDB, publicKey = defaultPublicKey, turtleDBFolder, redirect, reply) => {
20
+ const type = url.split('.').pop()
21
+ if (url === '/') {
22
+ console.log('let us index')
23
+ return redirect('/index.html')
24
+ }
25
+ try {
26
+ let directories = url.split('/')
27
+ if (directories[0] === '') directories.shift()
28
+ else throw new Error('url must start with /')
29
+ let urlPublicKey = publicKey
30
+ if (/^[0-9A-Za-z]{50}$/.test(directories[0])) {
31
+ urlPublicKey = directories.shift()
32
+ }
33
+ if (directories[directories.length - 1] === '') {
34
+ directories[directories.length - 1] = 'index.html'
35
+ return redirect(`/${urlPublicKey}/index.html`)
36
+ }
37
+ const unfilteredLength = directories.length
38
+ directories = directories.filter(Boolean)
39
+ if (unfilteredLength !== directories.length) {
40
+ return redirect(`/${urlPublicKey}/${directories.join('/')}`)
41
+ }
42
+ const turtleBranch = await turtleDB.summonBoundTurtleBranch(urlPublicKey)
43
+ const body = turtleBranch.lookupFile(directories.join('/'), false, +address)
44
+ if (body) {
45
+ return reply(type, body)
46
+ } else {
47
+ try {
48
+ const symlink = turtleBranch.lookupFile(directories[0])?.symlink
49
+ if (symlink) {
50
+ console.log(symlink)
51
+ const symlinkPublicKey = symlink.match(/.*\/(?<publicKey>[0-9A-Za-z]{50})$/)?.groups?.publicKey
52
+ if (symlinkPublicKey) {
53
+ return redirect(`/${symlinkPublicKey}/${directories.slice(1).join('/')}`)
54
+ }
55
+ }
56
+ const configJson = turtleBranch.lookupFile('config.json')
57
+ const packageJson = turtleBranch.lookupFile('package.json')
58
+ if (configJson) {
59
+ const config = JSON.parse(configJson)
60
+ logInfo(() => console.log({ config }))
61
+ const branchGroups = ['fsReadWrite', 'fsReadOnly']
62
+ for (const branchGroup of branchGroups) {
63
+ const branches = config[branchGroup]
64
+ if (branches) {
65
+ for (const { name, key } of branches) {
66
+ if (name && key) {
67
+ if (directories[0] === name) {
68
+ return redirect(`/${key}/${directories.slice(1).join('/')}`)
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ } else if (packageJson) {
75
+ const aliases = JSON.parse(packageJson).turtle.aliases
76
+ if (aliases && directories.join('/').startsWith(`${turtleDBFolder}/aliases/`)) {
77
+ directories.shift()
78
+ const name = directories.shift()
79
+ const key = aliases[name]
80
+ if (key) {
81
+ return redirect(`/${key}/${directories.join('/')}`)
82
+ }
83
+ }
84
+ }
85
+ } catch {
86
+ logDebug(() => console.log('not found, no config', url.pathname))
87
+ }
88
+ }
89
+ } catch (error) {
90
+ logError(() => console.error(error))
91
+ }
92
+ return reply(type, null)
93
+ }
@@ -0,0 +1,24 @@
1
+ export const OFF = Number.NEGATIVE_INFINITY
2
+ export const FATAL = -3
3
+ export const ERROR = -2
4
+ export const WARN = -1
5
+ export const INFO = 0
6
+ export const DEBUG = 1
7
+ export const TRACE = 2
8
+ export const SILLY = 3
9
+ export const ALL = Number.POSITIVE_INFINITY
10
+ export let logLevel = INFO
11
+ export const LOG_LEVELS = { OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE, SILLY, ALL }
12
+ export const setLogLevel = level => { logLevel = level }
13
+ export const log = (level, ...args) => {
14
+ if (level > +logLevel) return
15
+ if (args.length === 1 && typeof args[0] === 'function') args[0]()
16
+ else console.log(...args)
17
+ }
18
+ export const logFatal = (...args) => log(FATAL, ...args)
19
+ export const logError = (...args) => log(ERROR, ...args)
20
+ export const logWarn = (...args) => log(WARN, ...args)
21
+ export const logInfo = (...args) => log(INFO, ...args)
22
+ export const logDebug = (...args) => log(DEBUG, ...args)
23
+ export const logTrace = (...args) => log(TRACE, ...args)
24
+ export const logSilly = (...args) => log(SILLY, ...args)
@@ -0,0 +1,47 @@
1
+ import { logError, logInfo } from './logger.js'
2
+
3
+ let _tickCount = 0
4
+ let _nextTick
5
+ if (typeof setTimeout !== 'undefined') {
6
+ _nextTick = setTimeout
7
+ }
8
+ if (typeof process !== 'undefined') {
9
+ _nextTick = process.nextTick
10
+ }
11
+
12
+ let _functions = []
13
+ let _handlingTriggered = false
14
+
15
+ export const nextTick = f => {
16
+ _functions.push(f)
17
+ if (_handlingTriggered) return
18
+ _handlingTriggered = true
19
+ _nextTick(() => handleNextTick())
20
+ }
21
+
22
+ export const handleNextTick = (decorate = false) => {
23
+ ++_tickCount
24
+ if (decorate) logInfo(() => console.log(`šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡šŸ‘‡ --- _tickCount: ${_tickCount} ---\n\n`))
25
+ for (let i = 0; i < 10; ++i) {
26
+ _handlingTriggered = false
27
+ const __functions = _functions
28
+ _functions = []
29
+ __functions.forEach(f => f())
30
+ if (!_functions.length) {
31
+ if (decorate) logInfo(() => console.log('\n\nšŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘†šŸ‘† ---'))
32
+ return
33
+ }
34
+ if (decorate) logInfo(() => console.log(`\n\nšŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤”šŸ¤” --- _tickCount: ${_tickCount} loop: ${i} ---\n\n`))
35
+ }
36
+ _tickCount = Math.ceil(_tickCount)
37
+ logError(() => console.error('handleNextTick adding nextTick handler too many times'))
38
+ }
39
+
40
+ export const getTickCount = () => _tickCount
41
+
42
+ export const tics = async (count = 1, ticLabel = '') => {
43
+ for (let i = 0; i < count; ++i) {
44
+ if (ticLabel) logInfo(() => console.log(`${ticLabel}, tic: ${i}`))
45
+ await new Promise(resolve => setTimeout(resolve))
46
+ }
47
+ }