@wovin/core 0.1.36 → 0.2.2
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/README.md +0 -12
- package/dist/applog/applog-helpers.d.ts +12 -12
- package/dist/applog/applog-helpers.d.ts.map +1 -1
- package/dist/applog/applog-utils.d.ts +40 -6
- package/dist/applog/applog-utils.d.ts.map +1 -1
- package/dist/applog/datom-types.d.ts +67 -12
- package/dist/applog/datom-types.d.ts.map +1 -1
- package/dist/applog.d.ts +3 -3
- package/dist/applog.d.ts.map +1 -1
- package/dist/{applog.min.js → applog.js} +12 -7
- package/dist/blockstore.d.ts +1 -1
- package/dist/blockstore.d.ts.map +1 -1
- package/dist/{blockstore.min.js → blockstore.js} +1 -3
- package/dist/{blockstore.min.js.map → blockstore.js.map} +1 -1
- package/dist/chunk-22WDFLXO.js +138 -0
- package/dist/chunk-22WDFLXO.js.map +1 -0
- package/dist/chunk-3SUFNJEZ.js +1026 -0
- package/dist/chunk-3SUFNJEZ.js.map +1 -0
- package/dist/chunk-6ALNRM3J.js +435 -0
- package/dist/chunk-6ALNRM3J.js.map +1 -0
- package/dist/chunk-7Z5YDQKK.js +1 -0
- package/dist/{chunk-KXMTKPF4.min.js → chunk-BLF5MAWU.js} +8 -8
- package/dist/chunk-BLF5MAWU.js.map +1 -0
- package/dist/chunk-E46VTKTZ.js +1 -0
- package/dist/{chunk-H3VQJP56.min.js → chunk-HUIQ54TT.js} +9 -9
- package/dist/chunk-HUIQ54TT.js.map +1 -0
- package/dist/{chunk-BRC7LSM6.min.js → chunk-OC6Z6CQW.js} +5 -5
- package/dist/chunk-OC6Z6CQW.js.map +1 -0
- package/dist/chunk-SHUHRHOT.js +1923 -0
- package/dist/chunk-SHUHRHOT.js.map +1 -0
- package/dist/{chunk-QPGEBDMJ.min.js → chunk-YDAKBU6Q.js} +1 -1
- package/dist/chunk-YDAKBU6Q.js.map +1 -0
- package/dist/chunk-ZAADLBSB.js +36 -0
- package/dist/chunk-ZAADLBSB.js.map +1 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/{index.min.js → index.js} +81 -46
- package/dist/ipfs/car.d.ts +11 -11
- package/dist/ipfs/car.d.ts.map +1 -1
- package/dist/ipfs/ipfs-utils.d.ts +2 -2
- package/dist/ipfs/ipfs-utils.d.ts.map +1 -1
- package/dist/ipfs.d.ts +3 -3
- package/dist/ipfs.d.ts.map +1 -1
- package/dist/{ipfs.min.js → ipfs.js} +7 -10
- package/dist/ipns.d.ts +1 -1
- package/dist/ipns.d.ts.map +1 -1
- package/dist/ipns.js +64 -0
- package/dist/ipns.js.map +1 -0
- package/dist/pubsub/pub-pull.d.ts +3 -3
- package/dist/pubsub/pub-pull.d.ts.map +1 -1
- package/dist/pubsub/pubsub-types.d.ts +3 -3
- package/dist/pubsub/pubsub-types.d.ts.map +1 -1
- package/dist/pubsub/snap-push.d.ts +4 -4
- package/dist/pubsub/snap-push.d.ts.map +1 -1
- package/dist/pubsub/ucan.d.ts +1 -1
- package/dist/pubsub/ucan.d.ts.map +1 -1
- package/dist/pubsub.d.ts +4 -4
- package/dist/pubsub.d.ts.map +1 -1
- package/dist/{pubsub.min.js → pubsub.js} +7 -10
- package/dist/query/attr-helpers.d.ts +5 -0
- package/dist/query/attr-helpers.d.ts.map +1 -0
- package/dist/query/basic.d.ts +87 -23
- package/dist/query/basic.d.ts.map +1 -1
- package/dist/query/divergences.d.ts +5 -5
- package/dist/query/divergences.d.ts.map +1 -1
- package/dist/query/entity-collection.d.ts +19 -0
- package/dist/query/entity-collection.d.ts.map +1 -0
- package/dist/query/matchers.d.ts +12 -1
- package/dist/query/matchers.d.ts.map +1 -1
- package/dist/query/memoized.d.ts +66 -0
- package/dist/query/memoized.d.ts.map +1 -0
- package/dist/query/situations.d.ts +2 -1
- package/dist/query/situations.d.ts.map +1 -1
- package/dist/query/subscribable.d.ts +111 -0
- package/dist/query/subscribable.d.ts.map +1 -0
- package/dist/query/types.d.ts +54 -14
- package/dist/query/types.d.ts.map +1 -1
- package/dist/query.d.ts +9 -5
- package/dist/query.d.ts.map +1 -1
- package/dist/{query.min.js → query.js} +55 -34
- package/dist/retrieve/index.d.ts +1 -1
- package/dist/retrieve/index.d.ts.map +1 -1
- package/dist/retrieve/update-thread.d.ts +3 -3
- package/dist/retrieve/update-thread.d.ts.map +1 -1
- package/dist/retrieve.d.ts +1 -1
- package/dist/retrieve.d.ts.map +1 -1
- package/dist/retrieve.js +14 -0
- package/dist/thread/basic.d.ts +15 -19
- package/dist/thread/basic.d.ts.map +1 -1
- package/dist/thread/filters.d.ts +8 -10
- package/dist/thread/filters.d.ts.map +1 -1
- package/dist/thread/indexes.d.ts +57 -0
- package/dist/thread/indexes.d.ts.map +1 -0
- package/dist/thread/mapped.d.ts +40 -11
- package/dist/thread/mapped.d.ts.map +1 -1
- package/dist/thread/utils.d.ts +5 -5
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/writeable.d.ts +2 -2
- package/dist/thread/writeable.d.ts.map +1 -1
- package/dist/thread.d.ts +6 -5
- package/dist/thread.d.ts.map +1 -1
- package/dist/{thread.min.js → thread.js} +9 -6
- package/dist/types/typescript-utils.d.ts +6 -5
- package/dist/types/typescript-utils.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/{types.min.js → types.js} +3 -4
- package/dist/utils/debug-name.d.ts +13 -0
- package/dist/utils/debug-name.d.ts.map +1 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/package.json +32 -23
- package/src/applog/applog-helpers.ts +155 -0
- package/src/applog/applog-utils.test.ts +108 -0
- package/src/applog/applog-utils.ts +551 -0
- package/src/applog/datom-types.ts +167 -0
- package/src/applog/object-values.test.ts +106 -0
- package/src/applog.ts +3 -0
- package/src/blockstore/index.ts +36 -0
- package/src/blockstore.ts +1 -0
- package/src/index.ts +8 -0
- package/src/ipfs/car.ts +291 -0
- package/src/ipfs/fetch-snapshot-chain.ts +135 -0
- package/src/ipfs/ipfs-utils.ts +132 -0
- package/src/ipfs.ts +3 -0
- package/src/ipns/ipns-record.ts +115 -0
- package/src/ipns.ts +1 -0
- package/src/pubsub/UCAN Specs Overview.md +217 -0
- package/src/pubsub/connector.ts +9 -0
- package/src/pubsub/pub-pull.ts +31 -0
- package/src/pubsub/pubsub-types.ts +90 -0
- package/src/pubsub/snap-push.ts +278 -0
- package/src/pubsub/ucan-example.ts +61 -0
- package/src/pubsub/ucan.ts +56 -0
- package/src/pubsub.ts +4 -0
- package/src/query/attr-helpers.ts +5 -0
- package/src/query/basic.ts +1245 -0
- package/src/query/divergences.ts +50 -0
- package/src/query/entity-collection.ts +132 -0
- package/src/query/liveFilterAndMap.test.ts +102 -0
- package/src/query/matchers.ts +30 -0
- package/src/query/memoized.test.ts +151 -0
- package/src/query/memoized.ts +180 -0
- package/src/query/query-steps.ts +4 -0
- package/src/query/query.test.ts +538 -0
- package/src/query/situations.ts +261 -0
- package/src/query/subscribable.test.ts +245 -0
- package/src/query/subscribable.ts +234 -0
- package/src/query/types.ts +155 -0
- package/src/query/withoutDeleted.test.ts +204 -0
- package/src/query.ts +9 -0
- package/src/retrieve/index.ts +1 -0
- package/src/retrieve/update-thread.ts +248 -0
- package/src/retrieve.ts +1 -0
- package/src/test/perf/query.1m.perf.test.ts +94 -0
- package/src/test/perf/query.perf.test.ts +389 -0
- package/src/test/perf/query.realdata.perf.test.ts +182 -0
- package/src/thread/basic.ts +209 -0
- package/src/thread/filters.ts +227 -0
- package/src/thread/indexes.ts +256 -0
- package/src/thread/joinThreads.test.ts +304 -0
- package/src/thread/mapped.ts +226 -0
- package/src/thread/utils.ts +144 -0
- package/src/thread/writeable.ts +163 -0
- package/src/thread.ts +6 -0
- package/src/types/typescript-utils.ts +64 -0
- package/src/types.ts +1 -0
- package/src/utils/debug-name.ts +54 -0
- package/src/utils.ts +4 -0
- package/dist/chunk-2Y2PYHGR.min.js +0 -65
- package/dist/chunk-2Y2PYHGR.min.js.map +0 -1
- package/dist/chunk-5MMGBK2U.min.js +0 -1
- package/dist/chunk-7IDQIMQO.min.js +0 -1
- package/dist/chunk-BRC7LSM6.min.js.map +0 -1
- package/dist/chunk-COXXILXC.min.js +0 -512
- package/dist/chunk-COXXILXC.min.js.map +0 -1
- package/dist/chunk-GDX2OO7L.min.js +0 -9080
- package/dist/chunk-GDX2OO7L.min.js.map +0 -1
- package/dist/chunk-H3VQJP56.min.js.map +0 -1
- package/dist/chunk-HYMC7W6S.min.js +0 -1549
- package/dist/chunk-HYMC7W6S.min.js.map +0 -1
- package/dist/chunk-KEHU7HGZ.min.js +0 -5216
- package/dist/chunk-KEHU7HGZ.min.js.map +0 -1
- package/dist/chunk-KXMTKPF4.min.js.map +0 -1
- package/dist/chunk-PHITDXZT.min.js +0 -36
- package/dist/chunk-QO2KMGDN.min.js +0 -3771
- package/dist/chunk-QO2KMGDN.min.js.map +0 -1
- package/dist/chunk-QPGEBDMJ.min.js.map +0 -1
- package/dist/chunk-WXLCBTHX.min.js +0 -1606
- package/dist/chunk-WXLCBTHX.min.js.map +0 -1
- package/dist/ipns.min.js +0 -6419
- package/dist/ipns.min.js.map +0 -1
- package/dist/mobx/mobx-utils.d.ts +0 -82
- package/dist/mobx/mobx-utils.d.ts.map +0 -1
- package/dist/mobx.d.ts +0 -2
- package/dist/mobx.d.ts.map +0 -1
- package/dist/mobx.min.js +0 -141
- package/dist/retrieve.min.js +0 -17
- package/dist/types.min.js.map +0 -1
- package/dist/utils.min.js +0 -10
- package/dist/utils.min.js.map +0 -1
- /package/dist/{applog.min.js.map → applog.js.map} +0 -0
- /package/dist/{chunk-5MMGBK2U.min.js.map → chunk-7Z5YDQKK.js.map} +0 -0
- /package/dist/{chunk-7IDQIMQO.min.js.map → chunk-E46VTKTZ.js.map} +0 -0
- /package/dist/{chunk-PHITDXZT.min.js.map → index.js.map} +0 -0
- /package/dist/{index.min.js.map → ipfs.js.map} +0 -0
- /package/dist/{ipfs.min.js.map → pubsub.js.map} +0 -0
- /package/dist/{mobx.min.js.map → query.js.map} +0 -0
- /package/dist/{pubsub.min.js.map → retrieve.js.map} +0 -0
- /package/dist/{query.min.js.map → thread.js.map} +0 -0
- /package/dist/{retrieve.min.js.map → types.js.map} +0 -0
- /package/dist/{thread.min.js.map → utils.js.map} +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CID } from 'multiformats/cid'
|
|
2
|
+
import { cyrb53hash } from './../applog/applog-utils.ts'
|
|
3
|
+
import { AgentHash, AgentID, CidString } from '../applog/datom-types.ts'
|
|
4
|
+
import { Tagged } from '../types.ts'
|
|
5
|
+
import { UCANCapMap } from './ucan.ts'
|
|
6
|
+
type AgentString = Tagged<string, 'AgentString'>
|
|
7
|
+
type DIDString = Tagged<string, 'DID'>
|
|
8
|
+
export type { AgentHash, AgentString, DIDString }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface AppAgent {
|
|
12
|
+
ag: AgentHash
|
|
13
|
+
agentString: AgentString
|
|
14
|
+
did: DIDString
|
|
15
|
+
sign? (data: Uint8Array): Promise<Uint8Array>
|
|
16
|
+
ucan?: UCANCapMap
|
|
17
|
+
}
|
|
18
|
+
export interface AppAgentForWorker extends AppAgent {
|
|
19
|
+
signerSecret?: Uint8Array
|
|
20
|
+
}
|
|
21
|
+
export interface SnapRootBlock {
|
|
22
|
+
applogs: CID
|
|
23
|
+
applogsSignature: Uint8Array
|
|
24
|
+
info: CID
|
|
25
|
+
infoSignature: Uint8Array
|
|
26
|
+
prev?: CID
|
|
27
|
+
prevCounter?: number | null // Sequential counter: 0 for first, then +1 for each. null = unknown (legacy/error).
|
|
28
|
+
}
|
|
29
|
+
export interface SnapBlockLogs {
|
|
30
|
+
logs: CID[]
|
|
31
|
+
}
|
|
32
|
+
export interface SnapBlockChunks {
|
|
33
|
+
chunks: CID[]
|
|
34
|
+
}
|
|
35
|
+
export type SnapBlockLogsOrChunks = SnapBlockLogs | SnapBlockChunks
|
|
36
|
+
|
|
37
|
+
// HACK: this is actually note3 types, not wovin
|
|
38
|
+
export interface IShare {
|
|
39
|
+
id?: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
|
|
40
|
+
createdAt: string // ISO timestamp of creation
|
|
41
|
+
name: string // nick name for the pub
|
|
42
|
+
isDeleted?: boolean
|
|
43
|
+
purgeBeforePush?: boolean
|
|
44
|
+
|
|
45
|
+
pk: Uint8Array // exported privatekey - needed to create WritableName for publishing //TODO: store as non-extractable / encrypted?
|
|
46
|
+
|
|
47
|
+
autopush: boolean
|
|
48
|
+
lastPush: string | null
|
|
49
|
+
lastCID?: string
|
|
50
|
+
latestLogTs?: string
|
|
51
|
+
pubCounter?: number
|
|
52
|
+
|
|
53
|
+
publishedBy: string // local user appAgent
|
|
54
|
+
selectors?: string[] // to be used as a filter for which applogs to pub
|
|
55
|
+
encryptedFor?: string | null // short agentHash
|
|
56
|
+
encryptedWith?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and remote public)
|
|
57
|
+
|
|
58
|
+
// HACK WIP #39 - shared encryption
|
|
59
|
+
sharedKey?: CryptoKey | null // AES-GCM derived key from ECDH keys (local private and ipns public)
|
|
60
|
+
sharedAgents?: AgentID[] | null // array of string EntityIDs for the chosen agents (we need public jwkd atoms for each of them)
|
|
61
|
+
sharedKeyMap?: Map<AgentID, string> | null // uses public key from each agent to derive an aes key that is used to encrypt and btoa the sharedKey that is actually used to encrypt and decrypt the applogs
|
|
62
|
+
}
|
|
63
|
+
export interface ISubscription {
|
|
64
|
+
id: string // string hash of pub (used as unique id in IDB) `W3Name.create().toString()` starts with k51qzi5uqu5d
|
|
65
|
+
createdAt: string // ISO timestamp of creation
|
|
66
|
+
name: string // nick name for the pub
|
|
67
|
+
isDeleted: boolean
|
|
68
|
+
|
|
69
|
+
lastPull?: string | null
|
|
70
|
+
lastPullAttempt?: string | null
|
|
71
|
+
autopull: boolean
|
|
72
|
+
realtime?: boolean // enable real-time WebSocket updates via IPNS watcher
|
|
73
|
+
lastCID?: string // ? why not CidString
|
|
74
|
+
lastApplogCID?: string
|
|
75
|
+
publishedBy?: string // remote publisher short agentHash
|
|
76
|
+
encryptedFor?: string | undefined // short agentHash
|
|
77
|
+
encryptedWith?: CryptoKey | undefined // AES-GCM derived key from ECDH keys (local private and remote public)
|
|
78
|
+
}
|
|
79
|
+
export function isShare(obj: any): obj is IShare {
|
|
80
|
+
return obj?.pk !== undefined && obj?.lastPush !== undefined
|
|
81
|
+
}
|
|
82
|
+
export function isSubscription(obj: any): obj is ISubscription {
|
|
83
|
+
return obj?.lastPull !== undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type TShareSub = IShare | ISubscription
|
|
87
|
+
|
|
88
|
+
export function agentToShortHash(agentString: string) {
|
|
89
|
+
return cyrb53hash(agentString, 31, 7) as string
|
|
90
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import * as dagJson from '@ipld/dag-json'
|
|
2
|
+
import { Logger } from 'besonders-logger'
|
|
3
|
+
import { CID } from 'multiformats/cid'
|
|
4
|
+
import stringify from 'safe-stable-stringify'
|
|
5
|
+
import { ensureTsPvAndFinalizeApplog } from '../applog/applog-helpers.ts'
|
|
6
|
+
import type {
|
|
7
|
+
Applog,
|
|
8
|
+
ApplogArrayMaybeEncrypted,
|
|
9
|
+
ApplogArrayMaybeEncryptedRO,
|
|
10
|
+
ApplogArrayNoCIDMaybeEncryptedRO,
|
|
11
|
+
ApplogEnc,
|
|
12
|
+
ApplogEncNoCid,
|
|
13
|
+
CidString,
|
|
14
|
+
} from '../applog/datom-types.ts'
|
|
15
|
+
import { BlockStoreish, DecodedCar, getDecodedBlock, makeCarBlob } from '../ipfs/car.ts'
|
|
16
|
+
import { encodeBlockOriginal, prepareForPub } from '../ipfs/ipfs-utils.ts'
|
|
17
|
+
import { lastWriteWins } from './../query/basic.ts'
|
|
18
|
+
import { anyOf } from '../query/matchers.ts'
|
|
19
|
+
import { ApplogsOrThread, getLogsFromThread, Thread } from '../thread.ts'
|
|
20
|
+
import { rollingFilter } from '../thread/filters.ts'
|
|
21
|
+
import { keepTruthy } from '../utils.ts'
|
|
22
|
+
import type { AppAgent, IShare, SnapBlockChunks, SnapBlockLogs, SnapBlockLogsOrChunks } from './pubsub-types.ts'
|
|
23
|
+
|
|
24
|
+
const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line no-unused-vars
|
|
25
|
+
|
|
26
|
+
// export const neverEncryptAttrs = [
|
|
27
|
+
// 'agent/jwkd',
|
|
28
|
+
// 'agent/appAgent',
|
|
29
|
+
// 'pub/encryptedFor',
|
|
30
|
+
// 'pub/sharedKey',
|
|
31
|
+
// ]
|
|
32
|
+
|
|
33
|
+
// export interface WovinPublicationInfo {
|
|
34
|
+
// id: string
|
|
35
|
+
// }
|
|
36
|
+
|
|
37
|
+
export async function prepareSnapshotForPush(
|
|
38
|
+
agent: AppAgent,
|
|
39
|
+
appThread: Thread,
|
|
40
|
+
threadToPublish: ApplogsOrThread,
|
|
41
|
+
share: IShare,
|
|
42
|
+
prevSnapCID: CID | null,
|
|
43
|
+
prevCounter: number | null,
|
|
44
|
+
) {
|
|
45
|
+
if (prevCounter !== null && !prevSnapCID) {
|
|
46
|
+
throw ERROR('[prepareSnapshotForPush] prevCounter provided without prevSnapCID')
|
|
47
|
+
}
|
|
48
|
+
// TODO prevent publish if there is no new info
|
|
49
|
+
let logsToPublish = getLogsFromThread(threadToPublish)
|
|
50
|
+
|
|
51
|
+
// const logsFromLastPush = await getLogsFromPub(publication)
|
|
52
|
+
// logsToPublish = logsToPublish.filter(eachLog => !logsFromLastPush.includes(eachLog.cid)) // TODO deep compare includes
|
|
53
|
+
// const prevPushCIDs = [
|
|
54
|
+
// publication.lastCID,
|
|
55
|
+
// //TODO add this one and update the publication data after push
|
|
56
|
+
// ]
|
|
57
|
+
// const includedLogCIDs = [
|
|
58
|
+
// 'full array of CIDS from all previous pushes'
|
|
59
|
+
// ]
|
|
60
|
+
|
|
61
|
+
DEBUG(`[preparePubForPush] Collected ${logsToPublish.length} logs :`, {
|
|
62
|
+
logsToPublish,
|
|
63
|
+
threadOrLogsCount: (threadToPublish as any).nameAndSizeUntracked || (`[${(threadToPublish as any).length}]`),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const { sharedAgents, sharedKeyMap, sharedKey, pubCounter } = share ?? {}
|
|
67
|
+
|
|
68
|
+
const getExistingOrNewLog = (thread: Thread, share: IShare, ag: string, at: string, vl) => {
|
|
69
|
+
let logInQuestion = rollingFilter(lastWriteWins(thread), { en: share.id, at }).latestLog
|
|
70
|
+
if (!logInQuestion && vl !== undefined) {
|
|
71
|
+
logInQuestion = ensureTsPvAndFinalizeApplog({ ag, en: share.id, at, vl }, thread)
|
|
72
|
+
}
|
|
73
|
+
return logInQuestion // can be undefined if the passed vl is undefined and the log is not found
|
|
74
|
+
}
|
|
75
|
+
const shareNameLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/name', share.name)
|
|
76
|
+
|
|
77
|
+
// ? using did as it is derived from the same ecdh in note3 and part of the minimal AppAgent type required here in wovin core
|
|
78
|
+
const shareCounterLog = getExistingOrNewLog(appThread, share, agent.ag, 'share/counter', `${agent.did}<::>${pubCounter}`)
|
|
79
|
+
// ? discuss if this works to bind the counter to a specific derivation key - did is not necessarily derived from the same key by all lib users
|
|
80
|
+
|
|
81
|
+
const encryptApplog = async (applog: Applog, keyToUse: CryptoKey): Promise<Uint8Array> => {
|
|
82
|
+
const { log: eachLog, cid } = prepareForPub(applog) // without cid
|
|
83
|
+
const enc = new TextEncoder()
|
|
84
|
+
const stringified = stringify(eachLog)
|
|
85
|
+
const stringifiedEncodedAppLogPayload = enc.encode(stringified) // TODO: consider encodeToDagJson instead
|
|
86
|
+
VERBOSE('[odd]', { eachLog, stringified, stringifiedEncodedAppLogPayload })
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// @ts-expect-error
|
|
90
|
+
const encPayload = await agent.crypto?.aes.encrypt(stringifiedEncodedAppLogPayload, keyToUse, 'AES-GCM')
|
|
91
|
+
// TODO get rid of odd down here
|
|
92
|
+
VERBOSE('[odd] encrypted length:', stringifiedEncodedAppLogPayload.length, { encPayload })
|
|
93
|
+
return encPayload
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw ERROR('FAILED TO ENC payload length:', stringifiedEncodedAppLogPayload.length, { err })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// const decrypted = await decryptWithAesSharedKey(encPayload, keyToUse, 'string')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let maybeEncryptedApplogs: ApplogEncNoCid[] | readonly Applog[]
|
|
102
|
+
const encryptedApplogs = [] as { enc: Uint8Array }[]
|
|
103
|
+
const agentSharedKeyLogs = []
|
|
104
|
+
if (sharedAgents) { // encrypt all Applogs
|
|
105
|
+
if (!sharedKey || !sharedKeyMap) {
|
|
106
|
+
throw ERROR('sharedAgents but no Keys/Map', { sharedAgents, sharedKeyMap, sharedKey })
|
|
107
|
+
}
|
|
108
|
+
VERBOSE('encrypting', { sharedAgents, sharedKeyMap })
|
|
109
|
+
|
|
110
|
+
for (const [eachAgent, eachEncKey] of Array.from(sharedKeyMap.entries())) {
|
|
111
|
+
VERBOSE('adding key', { eachAgent, eachEncKey })
|
|
112
|
+
agentSharedKeyLogs.push({
|
|
113
|
+
ag: agent.ag,
|
|
114
|
+
en: eachAgent,
|
|
115
|
+
at: 'share/sharedKey',
|
|
116
|
+
vl: eachEncKey, // these are encrypted with the derived key from the local agent private and remote agent public keys
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
// const encryptedForLogs = await insertApplogsInAppDB(agentSharedKeyLogs)
|
|
120
|
+
// DEBUG(`[publish] adding agentSharedKeyLogs:`, encryptedForLogs)
|
|
121
|
+
const CIDlist: { cid: CidString; encCID?: CidString }[] = []
|
|
122
|
+
const pubCIDmap: Record<CidString, typeof CIDlist> = {}
|
|
123
|
+
// TODO ensure that all needed keys are in
|
|
124
|
+
for (const eachLog of logsToPublish) {
|
|
125
|
+
VERBOSE('[crypto] encrypting ', { eachLog, sharedKey })
|
|
126
|
+
// try {
|
|
127
|
+
const encPayload = await encryptApplog(eachLog, sharedKey)
|
|
128
|
+
DEBUG('[crypto] encrypted ', { eachLog, encPayload, sharedKey })
|
|
129
|
+
// } catch (err) {
|
|
130
|
+
// // its already traced in encryptAndTestDecrypt
|
|
131
|
+
// // continue
|
|
132
|
+
// }
|
|
133
|
+
encryptedApplogs.push({ enc: encPayload })
|
|
134
|
+
}
|
|
135
|
+
maybeEncryptedApplogs = encryptedApplogs
|
|
136
|
+
} else {
|
|
137
|
+
maybeEncryptedApplogs = logsToPublish // publish nonEncrypted
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
DEBUG('adding all agent info and shareAtoms', {
|
|
141
|
+
share,
|
|
142
|
+
agent,
|
|
143
|
+
logsToPublish,
|
|
144
|
+
// threadToPublish, - very verbose
|
|
145
|
+
agentSharedKeyLogs,
|
|
146
|
+
})
|
|
147
|
+
const infoLogs = [
|
|
148
|
+
...rollingFilter(lastWriteWins(appThread), { // TODO: use static filter for performance
|
|
149
|
+
en: agent.ag,
|
|
150
|
+
at: anyOf('agent/ecdh', 'agent/jwkd', 'agent/appAgent'),
|
|
151
|
+
}).applogs,
|
|
152
|
+
...(shareNameLog ? [shareNameLog] : []),
|
|
153
|
+
...(shareCounterLog ? [shareCounterLog] : []),
|
|
154
|
+
...agentSharedKeyLogs,
|
|
155
|
+
]
|
|
156
|
+
DEBUG(`[prepareSnapshotForPush] info logs:`, infoLogs)
|
|
157
|
+
if (!infoLogs.find(({ at }) => at === 'agent/appAgent')) throw ERROR(`[prepareSnapshotForPush] appThread missing agent/appAgent log`)
|
|
158
|
+
|
|
159
|
+
const applogsToEncode = keepTruthy(maybeEncryptedApplogs)
|
|
160
|
+
const infologsToEncode = keepTruthy(infoLogs)
|
|
161
|
+
if (!applogsToEncode.length) {
|
|
162
|
+
throw ERROR('no valid applogs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })
|
|
163
|
+
}
|
|
164
|
+
if (!infologsToEncode.length) {
|
|
165
|
+
throw ERROR('no valid infologs', { agent, maybeEncryptedApplogs, infoLogs, applogsToEncode, infologsToEncode, prevSnapCID })
|
|
166
|
+
}
|
|
167
|
+
const encodedSnapshot = await encodeSnapshotAsCar(agent, applogsToEncode, infologsToEncode, prevSnapCID, prevCounter)
|
|
168
|
+
DEBUG('inPrepareSnapshotForPush', { encodedSnapshot })
|
|
169
|
+
return encodedSnapshot
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @param applogs Encrypted or plain applogs
|
|
174
|
+
* @returns Car file
|
|
175
|
+
*/
|
|
176
|
+
export async function encodeSnapshotAsCar(
|
|
177
|
+
agent: AppAgent,
|
|
178
|
+
applogs: ApplogArrayNoCIDMaybeEncryptedRO,
|
|
179
|
+
infoLogs: readonly Applog[],
|
|
180
|
+
prevSnapCID: CID | null,
|
|
181
|
+
prevCounter: number | null,
|
|
182
|
+
) {
|
|
183
|
+
DEBUG(`[encodeSnapshotAsCar] encoding`, { agent, applogs, infoLogs })
|
|
184
|
+
const { cids: infoLogCids, encodedApplogs: encodedInfoLogs } = await encodeApplogsAsIPLD(infoLogs)
|
|
185
|
+
const { cids: applogCids, encodedApplogs } = await encodeApplogsAsIPLD(applogs)
|
|
186
|
+
let blocks = encodedApplogs.concat(encodedInfoLogs)
|
|
187
|
+
// We need to wrap the array to get a CID
|
|
188
|
+
const infoLogsWrap = await encodeBlockOriginal({ logs: infoLogCids })
|
|
189
|
+
blocks.push(infoLogsWrap)
|
|
190
|
+
const { rootCID: chunkRootCID, blocks: chunkBlocks } = await chunkApplogs(applogCids)
|
|
191
|
+
blocks = blocks.concat(chunkBlocks) // (i) concat bc. https://stackoverflow.com/a/51860949
|
|
192
|
+
const infoSignature = await agent.sign(infoLogsWrap.cid.bytes)
|
|
193
|
+
const applogsSignature = await agent.sign(chunkRootCID.bytes)
|
|
194
|
+
const root = {
|
|
195
|
+
info: infoLogsWrap.cid,
|
|
196
|
+
applogs: chunkRootCID,
|
|
197
|
+
infoSignature,
|
|
198
|
+
applogsSignature,
|
|
199
|
+
prev: prevSnapCID,
|
|
200
|
+
prevCounter: !prevSnapCID ? 0 : prevCounter !== null ? prevCounter + 1 : null,
|
|
201
|
+
}
|
|
202
|
+
DEBUG('[encodeSnapshotAsCar] encoding root', { root, logCids: applogCids, infoLogCids })
|
|
203
|
+
const encodedRoot = await encodeBlockOriginal(root)
|
|
204
|
+
blocks.push(encodedRoot)
|
|
205
|
+
DEBUG('[encodeSnapshotAsCar] => root', { encodedRoot })
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
cid: encodedRoot.cid,
|
|
209
|
+
blob: await makeCarBlob(encodedRoot.cid, blocks), // TODO: create CarBuilder (incl .encodeAndAdd({...}))
|
|
210
|
+
blocks,
|
|
211
|
+
infoLogCids,
|
|
212
|
+
applogCids,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** (i) IPFS has a block size limit of 1MB - which is about 15K CIDs */
|
|
217
|
+
export async function chunkApplogs(applogCids: CID<unknown, 297, 18, 1>[], size = 10000) {
|
|
218
|
+
if (!applogCids.length) throw ERROR(`[chunkApplogs] called with empty array`)
|
|
219
|
+
const chunks = []
|
|
220
|
+
// TODO: chunk by stable btree based on size or something like that
|
|
221
|
+
for (let i = 0; i < applogCids.length; i += size) {
|
|
222
|
+
const chunk = await encodeBlockOriginal({ logs: applogCids.slice(i, Math.min(i + applogCids.length, i + size)) })
|
|
223
|
+
chunks.push(chunk)
|
|
224
|
+
}
|
|
225
|
+
if (chunks.length === 1) return { rootCID: chunks[0].cid, blocks: chunks }
|
|
226
|
+
const root = await encodeBlockOriginal({ chunks: chunks.map(chunk => chunk.cid) })
|
|
227
|
+
const blocks = [root, ...chunks]
|
|
228
|
+
DEBUG(`[chunkApplogs] ${applogCids.length} logs chunked into ${chunks.length}`, { applogCids, root, blocks, chunks, dagJson })
|
|
229
|
+
return { rootCID: root.cid, blocks, chunks }
|
|
230
|
+
}
|
|
231
|
+
export async function unchunkApplogsBlock(block: SnapBlockLogsOrChunks, blockStore: BlockStoreish): Promise<CID[]> {
|
|
232
|
+
if (isSnapBlockChunks(block)) {
|
|
233
|
+
return (await Promise.all(
|
|
234
|
+
block.chunks.map(async (chunkCid) => {
|
|
235
|
+
const block = (await getDecodedBlock(blockStore, chunkCid)) as SnapBlockLogs
|
|
236
|
+
if (!block.logs) throw ERROR(`Weird chunk`, block)
|
|
237
|
+
return block.logs
|
|
238
|
+
}),
|
|
239
|
+
)).flat()
|
|
240
|
+
} else {
|
|
241
|
+
return block.logs
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
export function isSnapBlockChunks(block: SnapBlockLogsOrChunks): block is SnapBlockChunks {
|
|
245
|
+
return (block as any).chunks
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* @param applogs Encrypted or plain applogs
|
|
249
|
+
* @returns Car file
|
|
250
|
+
*/
|
|
251
|
+
export async function encodeSnapshotApplogsAsCar(
|
|
252
|
+
applogs: ApplogArrayMaybeEncryptedRO,
|
|
253
|
+
) {
|
|
254
|
+
const encoded = await encodeApplogsAsIPLD(applogs)
|
|
255
|
+
if (!encoded) throw ERROR('invalid applogs cannot continue', { applogs, encoded })
|
|
256
|
+
const { cids, encodedApplogs } = encoded
|
|
257
|
+
const root = { applogs: cids }
|
|
258
|
+
const encodedRoot = await encodeBlockOriginal(root)
|
|
259
|
+
DEBUG('[encodeSnapshotApplogsAsCar] encoded root', { cids, encodedRoot })
|
|
260
|
+
|
|
261
|
+
return await makeCarBlob(encodedRoot.cid, [encodedRoot, ...encodedApplogs])
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function encodeApplogsAsIPLD(applogs: ApplogArrayNoCIDMaybeEncryptedRO) {
|
|
265
|
+
DEBUG({ applogs })
|
|
266
|
+
const validApplogs = applogs.filter(eachLog => !!eachLog)
|
|
267
|
+
DEBUG({ validApplogs })
|
|
268
|
+
if (!validApplogs.length) throw ERROR('no valid applogs')
|
|
269
|
+
const preppedLogs = validApplogs.map(log => prepareForPub(log as Applog).log)
|
|
270
|
+
const encodedApplogs = await Promise.all(preppedLogs.map(encodeBlockOriginal))
|
|
271
|
+
DEBUG('[encodeApplogsAsIpld] encoded applogs', { preppedLogs, encodedApplogs })
|
|
272
|
+
|
|
273
|
+
const cids = encodedApplogs.map(b => {
|
|
274
|
+
if (!b.cid) throw ERROR(`[publish] no cid for encoded log:`, b)
|
|
275
|
+
return b.cid
|
|
276
|
+
})
|
|
277
|
+
return { cids, encodedApplogs }
|
|
278
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Capability } from 'iso-ucan/capability'
|
|
2
|
+
import { Store } from 'iso-ucan/store'
|
|
3
|
+
import { MemoryDriver } from 'iso-kv/drivers/memory.js'
|
|
4
|
+
import { EdDSASigner } from 'iso-signatures/signers/eddsa.js'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
import { verify } from 'iso-signatures/verifiers/eddsa.js'
|
|
7
|
+
import { Resolver } from 'iso-signatures/verifiers/resolver.js'
|
|
8
|
+
|
|
9
|
+
export const verifierResolver = new Resolver({
|
|
10
|
+
Ed25519: verify,
|
|
11
|
+
})
|
|
12
|
+
// from:
|
|
13
|
+
//https://ucan.xyz/guides/examples/
|
|
14
|
+
|
|
15
|
+
// Initialize delegation store for tracking capability chains
|
|
16
|
+
// In production, this might be backed by a database or persistent storage
|
|
17
|
+
const store = new Store(new MemoryDriver())
|
|
18
|
+
|
|
19
|
+
// Define file read capability with path validation schema
|
|
20
|
+
// The schema ensures all invocations include a valid file path
|
|
21
|
+
const FileReadCap = Capability.from({
|
|
22
|
+
schema: z.object({
|
|
23
|
+
path: z.string(), // Required: file path to read
|
|
24
|
+
}),
|
|
25
|
+
cmd: '/file/read', // UCAN v1 command identifier
|
|
26
|
+
verifierResolver,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Create cryptographic identities for resource owner and accessor
|
|
30
|
+
// In a real system, these would be persistent identity keypairs
|
|
31
|
+
const alice = await EdDSASigner.generate() // Resource owner
|
|
32
|
+
const bob = await EdDSASigner.generate() // Requesting access
|
|
33
|
+
|
|
34
|
+
const nowInSeconds = Math.floor(Date.now() / 1000)
|
|
35
|
+
|
|
36
|
+
// Alice grants Bob permission to read files
|
|
37
|
+
// This delegation can be stored, transmitted, or embedded in applications
|
|
38
|
+
const delegation = await FileReadCap.delegate({
|
|
39
|
+
iss: alice, // Alice issues this capability
|
|
40
|
+
aud: bob, // Bob is authorized to use it
|
|
41
|
+
sub: alice, // Alice's resources are the subject
|
|
42
|
+
pol: [], // No additional policy constraints
|
|
43
|
+
exp: nowInSeconds + 3600, // Expires in 1 hour for security
|
|
44
|
+
store,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Store delegation to enable later invocation validation
|
|
48
|
+
// The store enables automatic delegation chain resolution
|
|
49
|
+
// await store.set(delegation)
|
|
50
|
+
|
|
51
|
+
// Bob exercises the delegated capability to read a specific file
|
|
52
|
+
// This creates a cryptographically verifiable access request
|
|
53
|
+
const invocation = await FileReadCap.invoke({
|
|
54
|
+
iss: bob, // Bob is invoking the capability
|
|
55
|
+
sub: alice, // Alice's system will process the request
|
|
56
|
+
args: {
|
|
57
|
+
path: '/documents/report.pdf' // Specific file Bob wants to read
|
|
58
|
+
},
|
|
59
|
+
store, // Store containing the delegation proof
|
|
60
|
+
exp: nowInSeconds + 300, // Invocation expires in 5 minutes
|
|
61
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DelegationOptions, DelegationPayload, PayloadBase, StandardSchemaV1 } from 'iso-ucan/types'
|
|
2
|
+
import { Delegation } from 'iso-ucan/delegation'
|
|
3
|
+
import { AgentID } from '../applog/datom-types.ts'
|
|
4
|
+
import { Capability } from 'iso-ucan/capability'
|
|
5
|
+
import { z } from 'zod/mini'
|
|
6
|
+
import { verify } from 'iso-signatures/verifiers/eddsa.js'
|
|
7
|
+
import { Resolver } from 'iso-signatures/verifiers/resolver.js'
|
|
8
|
+
|
|
9
|
+
export const verifierResolver = new Resolver({
|
|
10
|
+
Ed25519: verify,// TODO review if iso-signatures is better than web crypto (or if it even uses it)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const WovinRootCap = Capability.from({
|
|
15
|
+
cmd: '/wovin',
|
|
16
|
+
schema: z.object({
|
|
17
|
+
agentString: z.string(),
|
|
18
|
+
agentID: z.string(),
|
|
19
|
+
type: z.string(),
|
|
20
|
+
}),
|
|
21
|
+
verifierResolver,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const WovinPublishCap = Capability.from({
|
|
25
|
+
cmd: '/wovin/publish',
|
|
26
|
+
schema: z.object({
|
|
27
|
+
agentString: z.string(),
|
|
28
|
+
agentID: z.string(),
|
|
29
|
+
type: z.string(),
|
|
30
|
+
}),
|
|
31
|
+
verifierResolver,
|
|
32
|
+
})
|
|
33
|
+
const WovinDelegateCap = Capability.from({
|
|
34
|
+
cmd: '/wovin/delegate',
|
|
35
|
+
schema: z.object({
|
|
36
|
+
agentString: z.string(),
|
|
37
|
+
agentID: z.string(),
|
|
38
|
+
type: z.string(),
|
|
39
|
+
}),
|
|
40
|
+
verifierResolver,
|
|
41
|
+
})
|
|
42
|
+
export enum KnownCaps {
|
|
43
|
+
publish = 'WovinPublishCap',
|
|
44
|
+
delegate = 'cap_delegate',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type DelegationPayloadSchema = StandardSchemaV1<unknown, DelegationPayload<unknown>>
|
|
48
|
+
export type DelegationOptionsStandard = DelegationOptions<DelegationPayloadSchema>
|
|
49
|
+
export type UCANCapMap = Partial<Record<KnownCaps, DelegationOptionsStandard[]>>
|
|
50
|
+
|
|
51
|
+
export type AgentCapMap = Record<AgentID, UCANCapMap>
|
|
52
|
+
|
|
53
|
+
export const createDelegation = (delegationPayload:DelegationOptionsStandard) => {
|
|
54
|
+
// delegationPayload.
|
|
55
|
+
return Delegation.create(delegationPayload)
|
|
56
|
+
}
|
package/src/pubsub.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Strip everything up to and including the first `/` */
|
|
2
|
+
export type StripFirstPrefix<S extends string> = S extends `${string}/${infer Rest}` ? Rest : S
|
|
3
|
+
|
|
4
|
+
/** Strip a specific prefix `P/` from attribute string */
|
|
5
|
+
export type StripExplicitPrefix<S extends string, P extends string> = S extends `${P}/${infer Rest}` ? Rest : S
|