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.
- package/LICENSE +661 -0
- package/bin/turtb.js +259 -0
- package/lib/display/h.js +204 -0
- package/lib/display/helpers.js +78 -0
- package/lib/display/render.js +177 -0
- package/lib/display/shapes.js +91 -0
- package/lib/turtle/Signer.js +120 -0
- package/lib/turtle/Signer.test.js +38 -0
- package/lib/turtle/TurtleBranch.js +147 -0
- package/lib/turtle/TurtleBranch.test.js +89 -0
- package/lib/turtle/TurtleDictionary.js +157 -0
- package/lib/turtle/TurtleDictionary.test.js +331 -0
- package/lib/turtle/U8aTurtle.js +203 -0
- package/lib/turtle/U8aTurtle.test.js +60 -0
- package/lib/turtle/Workspace.js +62 -0
- package/lib/turtle/Workspace.test.js +63 -0
- package/lib/turtle/codecs/CodecType.js +36 -0
- package/lib/turtle/codecs/CodecTypeVersion.js +37 -0
- package/lib/turtle/codecs/Commit.js +10 -0
- package/lib/turtle/codecs/CompositeCodec.js +86 -0
- package/lib/turtle/codecs/TreeNode.js +38 -0
- package/lib/turtle/codecs/codec.js +441 -0
- package/lib/turtle/connections/AbstractUpdater.js +176 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.js +102 -0
- package/lib/turtle/connections/TurtleBranchMultiplexer.test.js +26 -0
- package/lib/turtle/connections/TurtleBranchUpdater.js +47 -0
- package/lib/turtle/connections/TurtleDB.js +165 -0
- package/lib/turtle/connections/TurtleDB.test.js +45 -0
- package/lib/turtle/connections/TurtleTalker.js +34 -0
- package/lib/turtle/connections/TurtleTalker.test.js +101 -0
- package/lib/turtle/utils.js +192 -0
- package/lib/turtle/utils.test.js +158 -0
- package/lib/utils/Assert.js +115 -0
- package/lib/utils/NestedSet.js +68 -0
- package/lib/utils/NestedSet.test.js +30 -0
- package/lib/utils/OWN_KEYS.js +1 -0
- package/lib/utils/Recaller.js +175 -0
- package/lib/utils/Recaller.test.js +75 -0
- package/lib/utils/TestRunner.js +200 -0
- package/lib/utils/TestRunner.test.js +144 -0
- package/lib/utils/TestRunnerConstants.js +13 -0
- package/lib/utils/combineUint8ArrayLikes.js +18 -0
- package/lib/utils/combineUint8Arrays.js +23 -0
- package/lib/utils/components.js +88 -0
- package/lib/utils/crypto.js +17 -0
- package/lib/utils/deepEqual.js +16 -0
- package/lib/utils/deepEqual.test.js +27 -0
- package/lib/utils/fileTransformer.js +16 -0
- package/lib/utils/handleRedirect.js +93 -0
- package/lib/utils/logger.js +24 -0
- package/lib/utils/nextTick.js +47 -0
- package/lib/utils/noble-secp256k1.js +602 -0
- package/lib/utils/proxyWithRecaller.js +51 -0
- package/lib/utils/toCombinedVersion.js +14 -0
- package/lib/utils/toSubVersions.js +14 -0
- package/lib/utils/toVersionCount.js +5 -0
- package/lib/utils/webSocketMuxFactory.js +123 -0
- package/lib/utils/zabacaba.js +25 -0
- package/package.json +24 -0
- package/src/ArchiveUpdater.js +99 -0
- package/src/S3Updater.js +99 -0
- package/src/archiveSync.js +28 -0
- package/src/fileSync.js +155 -0
- package/src/getExistenceLength.js +19 -0
- package/src/manageCert.js +36 -0
- package/src/originSync.js +75 -0
- package/src/outletSync.js +50 -0
- package/src/proxyFolder.js +195 -0
- package/src/s3Sync.js +32 -0
- 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
|
+
}
|