├── .gitattributes ├── src ├── types │ ├── random-access-file.d.ts │ ├── random-access-memory.d.ts │ ├── bs58.d.ts │ ├── noise-peer.d.ts │ ├── hypercore-crypto.d.ts │ ├── simple-message-channels.d.ts │ └── sodium-native.d.ts ├── WeakCache.ts ├── PeerMsg.ts ├── Debug.ts ├── index.ts ├── JsonBuffer.ts ├── SqlDatabase.ts ├── migrations │ └── 0001_initial_schema.sql ├── SwarmInterface.ts ├── MessageBus.ts ├── Block.ts ├── KeyStore.ts ├── hypercore.ts ├── MapSet.ts ├── MessageRouter.ts ├── Crawler.ts ├── Heartbeat.ts ├── Queue.ts ├── Keys.ts ├── TraverseLogic.ts ├── FileStore.ts └── Repo.ts ├── .gitignore ├── examples ├── simple-repos │ ├── .gitignore │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── main.ts ├── chat │ ├── README.md │ ├── src │ │ ├── chat.ts │ │ ├── diffy.d.ts │ │ ├── ui.ts │ │ └── channel.ts │ └── package.json └── simple │ ├── package.json │ ├── tsconfig.json │ └── src │ └── simple.ts ├── tests ├── tsconfig.json ├── NetworkPeer.test.ts ├── KeyStore.test.ts ├── CursorStore.test.ts ├── TraverseLogic.test.ts ├── Heartbeat.test.ts ├── PeerConnection.test.ts ├── Crypto.test.ts ├── ReplicationManager.test.ts ├── FileServer.test.ts ├── FileStore.test.ts └── Network.test.ts ├── dist ├── PeerMsg.js.map ├── RepoMsg.js.map ├── PeerMsg.js ├── RepoMsg.js ├── SwarmInterface.js.map ├── SwarmInterface.js ├── Block.d.ts ├── SqlDatabase.d.ts ├── hypercore.d.ts ├── TraverseLogic.d.ts ├── JsonBuffer.d.ts ├── WeakCache.d.ts ├── WeakCache.js.map ├── index.js.map ├── Debug.d.ts ├── WeakCache.js ├── MapSet.d.ts ├── KeyStore.d.ts ├── Queue.d.ts ├── MessageBus.d.ts ├── Crawler.d.ts ├── PeerMsg.d.ts ├── FileServerClient.d.ts ├── Debug.js.map ├── FileServer.d.ts ├── index.d.ts ├── SqlDatabase.js.map ├── JsonBuffer.js.map ├── MessageRouter.d.ts ├── FileStore.d.ts ├── Heartbeat.d.ts ├── migrations │ └── 0001_initial_schema.sql ├── KeyStore.js.map ├── JsonBuffer.js ├── Keys.js.map ├── SwarmInterface.d.ts ├── KeyStore.js ├── PeerConnection.d.ts ├── SqlDatabase.js ├── Clock.d.ts ├── MessageRouter.js.map ├── CursorStore.d.ts ├── ReplicationManager.d.ts ├── Block.js.map ├── TraverseLogic.js.map ├── MessageBus.js.map ├── Network.d.ts ├── Actor.d.ts ├── Handle.d.ts ├── DocFrontend.d.ts ├── StreamLogic.d.ts ├── Repo.js ├── CryptoClient.d.ts ├── Repo.js.map ├── Keys.d.ts ├── MessageRouter.js ├── MapSet.js ├── DocBackend.d.ts ├── Queue.js.map ├── hypercore.js.map ├── Crawler.js.map ├── Debug.js ├── Repo.d.ts ├── MapSet.js.map ├── hypercore.js ├── Heartbeat.js.map ├── FileStore.js.map ├── ClockStore.d.ts ├── NetworkPeer.d.ts ├── Heartbeat.js ├── Queue.js ├── TraverseLogic.js ├── Metadata.d.ts ├── index.js ├── Keys.js ├── CursorStore.js.map ├── MessageBus.js ├── Block.js ├── RepoFrontend.d.ts ├── PeerConnection.js.map ├── Handle.js.map ├── NetworkPeer.js.map ├── FeedStore.d.ts ├── Misc.d.ts ├── CryptoClient.js.map ├── FileServerClient.js.map ├── Crawler.js ├── Misc.js.map ├── RepoBackend.d.ts ├── Handle.js ├── CursorStore.js ├── ReplicationManager.js.map └── PeerConnection.js ├── .prettierrc ├── .vscode └── settings.json ├── tools ├── Cat.ts ├── Meta.ts ├── Peek.ts ├── Serve.ts ├── Watch.ts └── cli.ts ├── tsconfig.json ├── .circleci └── config.yml ├── LICENSE └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/* linguist-generated=true 2 | -------------------------------------------------------------------------------- /src/types/random-access-file.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'random-access-file' 2 | -------------------------------------------------------------------------------- /src/types/random-access-memory.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'random-access-memory' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | *.log 4 | .nyc_output 5 | coverage 6 | .data 7 | -------------------------------------------------------------------------------- /examples/simple-repos/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | *.log 4 | .data 5 | repo-a 6 | repo-b -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": [".", "../src"] 4 | } 5 | -------------------------------------------------------------------------------- /dist/PeerMsg.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"PeerMsg.js","sourceRoot":"","sources":["../src/PeerMsg.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/RepoMsg.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"RepoMsg.js","sourceRoot":"","sources":["../src/RepoMsg.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/PeerMsg.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=PeerMsg.js.map -------------------------------------------------------------------------------- /dist/RepoMsg.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=RepoMsg.js.map -------------------------------------------------------------------------------- /dist/SwarmInterface.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"SwarmInterface.js","sourceRoot":"","sources":["../src/SwarmInterface.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/SwarmInterface.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=SwarmInterface.js.map -------------------------------------------------------------------------------- /src/types/bs58.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'bs58' { 2 | function encode(buffer: Buffer | Array): string 3 | function decode(str: string): Buffer 4 | } 5 | -------------------------------------------------------------------------------- /examples/simple-repos/README.md: -------------------------------------------------------------------------------- 1 | # Basic code for working with HyperMerge Repos 2 | 3 | ## Install and Run 4 | ``` 5 | $ yarn 6 | $ yarn test 7 | ``` 8 | -------------------------------------------------------------------------------- /dist/Block.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare function pack(obj: Object): Buffer; 3 | export declare function unpack(data: Uint8Array): any; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "arrowParens": "always", 4 | "trailingComma": "es5", 5 | "tabWidth": 2, 6 | "semi": false, 7 | "singleQuote": true 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.format.enable": false, 3 | "editor.formatOnSave": true, 4 | "typescript.tsdk": "node_modules/typescript/lib", 5 | "editor.rulers": [100] 6 | } 7 | -------------------------------------------------------------------------------- /dist/SqlDatabase.d.ts: -------------------------------------------------------------------------------- 1 | import { Database } from 'better-sqlite3'; 2 | export { Database, Statement } from 'better-sqlite3'; 3 | export declare function open(storage: string, memory: boolean): Database; 4 | -------------------------------------------------------------------------------- /dist/hypercore.d.ts: -------------------------------------------------------------------------------- 1 | import { Feed } from 'hypercore'; 2 | import { ActorId } from './Misc'; 3 | export declare function readFeed(id: ActorId | 'ledger', feed: Feed, cb: (data: T[]) => void): void; 4 | -------------------------------------------------------------------------------- /dist/TraverseLogic.d.ts: -------------------------------------------------------------------------------- 1 | export declare const WARNING_STACK_SIZE = 2000; 2 | export interface SelectFn { 3 | (obj: unknown): boolean; 4 | } 5 | export declare function iterativeDfs(select: SelectFn, root: unknown): T[]; 6 | -------------------------------------------------------------------------------- /dist/JsonBuffer.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare function parse(buffer: ArrayBuffer | ArrayBufferView): any; 3 | export declare function bufferify(value: any): Buffer; 4 | export declare function parseAllValid(buffers: Uint8Array[]): any[]; 5 | -------------------------------------------------------------------------------- /dist/WeakCache.d.ts: -------------------------------------------------------------------------------- 1 | export interface Create { 2 | (key: K): V; 3 | } 4 | export default class WeakCache extends WeakMap { 5 | private create; 6 | constructor(create: Create); 7 | getOrCreate(key: K): V; 8 | } 9 | -------------------------------------------------------------------------------- /dist/WeakCache.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"WeakCache.js","sourceRoot":"","sources":["../src/WeakCache.ts"],"names":[],"mappings":";;AAAA,iCAAoC;AAMpC,MAAqB,SAA+B,SAAQ,OAAa;IACvE,YAAoB,MAAoB;QACtC,KAAK,EAAE,CAAA;QADW,WAAM,GAAN,MAAM,CAAc;IAExC,CAAC;IAED,WAAW,CAAC,GAAM;QAChB,OAAO,kBAAW,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5C,CAAC;CACF;AARD,4BAQC"} -------------------------------------------------------------------------------- /src/WeakCache.ts: -------------------------------------------------------------------------------- 1 | import { getOrCreate } from './Misc' 2 | 3 | export interface Create { 4 | (key: K): V 5 | } 6 | 7 | export default class WeakCache extends WeakMap { 8 | constructor(private create: Create) { 9 | super() 10 | } 11 | 12 | getOrCreate(key: K): V { 13 | return getOrCreate(this, key, this.create) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAEA,+BAA6B;AAApB,4FAAA,IAAI,OAAA;AACb,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AACpB,+CAA6C;AAApC,4GAAA,YAAY,OAAA;AAGrB,2CAAyC;AAAhC,wGAAA,UAAU,OAAA;AACnB,6CAA2C;AAAlC,0GAAA,WAAW,OAAA;AAIpB,+CAA6C;AAApC,4GAAA,YAAY,OAAA;AACrB,iDAAkC;AACzB,wBAAM"} -------------------------------------------------------------------------------- /dist/Debug.d.ts: -------------------------------------------------------------------------------- 1 | import Debug, { IDebugger } from 'debug'; 2 | export { IDebugger }; 3 | export declare const log: Debug.IDebugger; 4 | declare const _default: (namespace: string) => Debug.IDebugger; 5 | export default _default; 6 | export declare const trace: (label: string) => (x: T, ...args: any[]) => T; 7 | export declare function assignGlobal(objs: { 8 | [name: string]: any; 9 | }): void; 10 | -------------------------------------------------------------------------------- /src/PeerMsg.ts: -------------------------------------------------------------------------------- 1 | import { DocId } from './Misc' 2 | import * as Clock from './Clock' 3 | 4 | export type PeerMsg = DocumentMsg | CursorMsg 5 | 6 | interface DocumentMsg { 7 | type: 'DocumentMessage' 8 | id: DocId 9 | contents: any 10 | } 11 | 12 | interface CursorMsg { 13 | type: 'CursorMessage' 14 | cursors: { docId: DocId; cursor: Clock.Clock }[] 15 | clocks: { docId: DocId; clock: Clock.Clock }[] 16 | } 17 | -------------------------------------------------------------------------------- /dist/WeakCache.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const Misc_1 = require("./Misc"); 4 | class WeakCache extends WeakMap { 5 | constructor(create) { 6 | super(); 7 | this.create = create; 8 | } 9 | getOrCreate(key) { 10 | return Misc_1.getOrCreate(this, key, this.create); 11 | } 12 | } 13 | exports.default = WeakCache; 14 | //# sourceMappingURL=WeakCache.js.map -------------------------------------------------------------------------------- /dist/MapSet.d.ts: -------------------------------------------------------------------------------- 1 | export default class MapSet { 2 | private map; 3 | add(key: A, val: B): boolean; 4 | set(key: A, val: Set): void; 5 | values(): Set[]; 6 | union(): Set; 7 | keys(): A[]; 8 | merge(key: A, vals: B[]): boolean; 9 | delete(key: A): Set; 10 | remove(key: A, val: B): void; 11 | keysWith(val: B): Set; 12 | get(key: A): Set; 13 | has(key: A, val: B): boolean; 14 | } 15 | -------------------------------------------------------------------------------- /tools/Cat.ts: -------------------------------------------------------------------------------- 1 | import { Repo, DocUrl } from '../src' 2 | import Hyperswarm from 'hyperswarm' 3 | 4 | const url = process.argv[2] as DocUrl 5 | 6 | if (url === undefined) { 7 | console.log('Usage: cat ') 8 | process.exit() 9 | } 10 | 11 | const repo = new Repo({ memory: true }) 12 | 13 | repo.setSwarm(Hyperswarm()) 14 | 15 | console.log('Watching document:', url) 16 | 17 | repo.watch(url, (doc) => { 18 | console.log(doc) 19 | }) 20 | -------------------------------------------------------------------------------- /dist/KeyStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Database } from './SqlDatabase'; 3 | import * as Keys from './Keys'; 4 | export default class KeyStore { 5 | private db; 6 | private preparedGet; 7 | private preparedSet; 8 | private preparedClear; 9 | constructor(db: Database); 10 | get(name: string): Keys.KeyBuffer | undefined; 11 | set(name: string, keyPair: Keys.KeyBuffer): Keys.KeyBuffer; 12 | clear(name: string): void; 13 | } 14 | -------------------------------------------------------------------------------- /dist/Queue.d.ts: -------------------------------------------------------------------------------- 1 | export default class Queue { 2 | push: (item: T) => void; 3 | name: string; 4 | private queue; 5 | private log; 6 | private subscription?; 7 | constructor(name?: string); 8 | first(): Promise; 9 | drain(fn: (item: T) => void): void; 10 | once(subscriber: (item: T) => void): void; 11 | subscribe(subscriber: (item: T) => void): void; 12 | unsubscribe(): void; 13 | get length(): number; 14 | private enqueue; 15 | } 16 | -------------------------------------------------------------------------------- /dist/MessageBus.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import Queue from './Queue'; 3 | export default class MessageBus { 4 | stream: NodeJS.ReadWriteStream; 5 | sendQ: Queue; 6 | receiveQ: Queue; 7 | constructor(stream: NodeJS.ReadWriteStream, subscriber?: (msg: Msg) => void); 8 | onData: (data: Buffer) => void; 9 | send(msg: Msg): void; 10 | subscribe(onMsg: (msg: Msg) => void): void; 11 | unsubscribe(): void; 12 | close(): void; 13 | } 14 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | # hypermerge chat 2 | 3 | hypermerge chat is a simple chat application for the console. 4 | 5 | It was built to serve as an real-world example of how to 6 | use the [hypermerge](https://github.com/automerge/hypermerge) library. 7 | 8 | ## Basic usage 9 | 10 | To create a channel: 11 | 12 | `yarn run chat --nick yourname` 13 | 14 | To join an existing channel: 15 | 16 | `yarn run chat --nick yourname hypermerge:/` 17 | 18 | ## License 19 | 20 | MIT 21 | -------------------------------------------------------------------------------- /dist/Crawler.d.ts: -------------------------------------------------------------------------------- 1 | import { RepoFrontend } from './RepoFrontend'; 2 | import { DocUrl, BaseUrl } from './Misc'; 3 | import { Handle } from './Handle'; 4 | import { Doc } from 'automerge'; 5 | export declare class Crawler { 6 | repo: RepoFrontend; 7 | seen: Set; 8 | handles: Map>; 9 | constructor(repo: RepoFrontend); 10 | crawl(url: DocUrl): void; 11 | onUrl: (urlVal: BaseUrl) => void; 12 | onDocumentUpdate: (doc: Doc) => void; 13 | close(): void; 14 | } 15 | -------------------------------------------------------------------------------- /dist/PeerMsg.d.ts: -------------------------------------------------------------------------------- 1 | import { DocId } from './Misc'; 2 | import * as Clock from './Clock'; 3 | export declare type PeerMsg = DocumentMsg | CursorMsg; 4 | interface DocumentMsg { 5 | type: 'DocumentMessage'; 6 | id: DocId; 7 | contents: any; 8 | } 9 | interface CursorMsg { 10 | type: 'CursorMessage'; 11 | cursors: { 12 | docId: DocId; 13 | cursor: Clock.Clock; 14 | }[]; 15 | clocks: { 16 | docId: DocId; 17 | clock: Clock.Clock; 18 | }[]; 19 | } 20 | export {}; 21 | -------------------------------------------------------------------------------- /examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hm-simple-example", 3 | "version": "0.0.1", 4 | "description": "Simple example of two repo's sharing data", 5 | "main": "./simple.ts", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "ts-node src/simple.ts" 9 | }, 10 | "dependencies": { 11 | "dat-swarm-defaults": "^1.0.2", 12 | "discovery-swarm": "^5.1.3", 13 | "hypermerge": "github:automerge/hypermerge#fork", 14 | "random-access-memory": "^2.4.0", 15 | "ts-node": "^7.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Debug.ts: -------------------------------------------------------------------------------- 1 | import * as Keys from './Keys' 2 | import Debug, { IDebugger } from 'debug' 3 | 4 | Debug.formatters.b = Keys.encode 5 | 6 | export { IDebugger } 7 | export const log = Debug('hypermerge') 8 | 9 | export default (namespace: string) => log.extend(namespace) 10 | 11 | export const trace = (label: string) => (x: T, ...args: any[]): T => { 12 | console.log(`${label}:`, x, ...args) 13 | return x 14 | } 15 | 16 | export function assignGlobal(objs: { [name: string]: any }) { 17 | Object.assign(global, objs) 18 | } 19 | -------------------------------------------------------------------------------- /examples/simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "typeRoots": [ 6 | "./node_modules/@types" 7 | ], 8 | "lib": [ 9 | "es6", 10 | "es2017" 11 | ], 12 | "sourceMap": true, 13 | "module": "commonjs", 14 | "target": "es6", 15 | "esModuleInterop": true, 16 | "allowSyntheticDefaultImports": true, 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "noFallthroughCasesInSwitch": true 20 | }, 21 | "include": [ 22 | "src" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /examples/simple-repos/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "typeRoots": ["./src/types", "./node_modules/@types", "./src/node_modules/@types"], 6 | "lib": [ 7 | "es6", 8 | "es2017" 9 | ], 10 | "sourceMap": true, 11 | "module": "commonjs", 12 | "target": "es6", 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "skipLibCheck": true, 16 | "strict": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "include": [ 20 | "src" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /dist/FileServerClient.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import * as http from 'http'; 3 | import { Readable } from 'stream'; 4 | import { HyperfileUrl } from './Misc'; 5 | import { Header } from './FileStore'; 6 | export default class FileServerClient { 7 | serverPath: Promise; 8 | agent: http.Agent; 9 | setServerPath: (path: string) => void; 10 | constructor(); 11 | write(stream: Readable, mimeType: string): Promise
; 12 | header(url: HyperfileUrl): Promise
; 13 | read(url: HyperfileUrl): Promise<[Header, Readable]>; 14 | private request; 15 | } 16 | -------------------------------------------------------------------------------- /dist/Debug.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Debug.js","sourceRoot":"","sources":["../src/Debug.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA8B;AAC9B,kDAAwC;AAExC,eAAK,CAAC,UAAU,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;AAGnB,QAAA,GAAG,GAAG,eAAK,CAAC,YAAY,CAAC,CAAA;AAEtC,kBAAe,CAAC,SAAiB,EAAE,EAAE,CAAC,WAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;AAE9C,QAAA,KAAK,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,CAAI,CAAI,EAAE,GAAG,IAAW,EAAK,EAAE;IACrE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,CAAA;IACpC,OAAO,CAAC,CAAA;AACV,CAAC,CAAA;AAED,SAAgB,YAAY,CAAC,IAA6B;IACxD,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AAC7B,CAAC;AAFD,oCAEC"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "declaration": true, 5 | "typeRoots": ["./src/types", "./node_modules/@types", "./src/node_modules/@types"], 6 | "lib": ["es6", "es2017"], 7 | "sourceMap": true, 8 | "module": "commonjs", 9 | "target": "es6", 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "skipLibCheck": true, 13 | "strict": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noImplicitAny": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /dist/FileServer.d.ts: -------------------------------------------------------------------------------- 1 | import FileStore from './FileStore'; 2 | export interface HostAndPort { 3 | host: string; 4 | port: number; 5 | } 6 | export default class FileServer { 7 | private files; 8 | private http; 9 | constructor(store: FileStore); 10 | listen(pathOrAddress: string | HostAndPort): Promise; 11 | isListening(): boolean; 12 | close(): Promise; 13 | private onConnection; 14 | /** 15 | * Handles incoming connections, and can respond by throwing FileServerError. 16 | */ 17 | private onConnectionUnsafe; 18 | private upload; 19 | private sendHeaders; 20 | } 21 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { Patch, Doc, Proxy, Proxy as EditDoc, ChangeFn } from 'automerge'; 2 | export { Repo } from './Repo'; 3 | export { Handle } from './Handle'; 4 | export { RepoBackend } from './RepoBackend'; 5 | export { RepoFrontend } from './RepoFrontend'; 6 | export { ToFrontendRepoMsg, ToBackendRepoMsg } from './RepoMsg'; 7 | export { DocBackend } from './DocBackend'; 8 | export { DocFrontend } from './DocFrontend'; 9 | export { DocUrl, HyperfileUrl } from './Misc'; 10 | export { Header as HyperfileHeader } from './FileStore'; 11 | export { CryptoClient } from './CryptoClient'; 12 | import * as Crypto from './Crypto'; 13 | export { Crypto }; 14 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { Patch, Doc, Proxy, Proxy as EditDoc, ChangeFn } from 'automerge' 2 | 3 | export { Repo } from './Repo' 4 | export { Handle } from './Handle' 5 | export { RepoBackend } from './RepoBackend' 6 | export { RepoFrontend } from './RepoFrontend' 7 | export { ToFrontendRepoMsg, ToBackendRepoMsg } from './RepoMsg' 8 | 9 | export { DocBackend } from './DocBackend' 10 | export { DocFrontend } from './DocFrontend' 11 | export { DocUrl, HyperfileUrl } from './Misc' 12 | export { Header as HyperfileHeader } from './FileStore' 13 | 14 | export { CryptoClient } from './CryptoClient' 15 | import * as Crypto from './Crypto' 16 | export { Crypto } 17 | -------------------------------------------------------------------------------- /examples/chat/src/chat.ts: -------------------------------------------------------------------------------- 1 | import Minimist from 'minimist' 2 | import Prompt from 'prompt-sync' 3 | import Channel from './channel' 4 | import initUI from './ui' 5 | 6 | const argv = Minimist(process.argv.slice(2)) 7 | 8 | if (argv.help || argv._.length > 1) { 9 | console.log('Usage: hm-chat --nick= []\n') 10 | process.exit(0) 11 | } 12 | 13 | let nick = argv.nick 14 | if (!argv.nick) { 15 | const prompt = Prompt() 16 | nick = prompt('Enter your nickname: ') 17 | } 18 | 19 | const channelKey = argv._[0] 20 | 21 | const channel = new Channel(nick, channelKey) 22 | channel.once('ready', () => initUI(channel)) 23 | channel.ready() 24 | -------------------------------------------------------------------------------- /tools/Meta.ts: -------------------------------------------------------------------------------- 1 | 2 | import fs from "fs" 3 | import { Repo } from "../src" 4 | const raf: Function = require("random-access-file") 5 | const id = process.argv[2] 6 | const _path = process.argv[3] 7 | const path = _path || ".data" 8 | 9 | if (path === undefined || id === undefined) { 10 | console.log("Usage: meta ID [REPO]") 11 | process.exit() 12 | } 13 | 14 | if (_path && !fs.existsSync(_path + "/ledger")) { 15 | console.log("No repo found: " + _path) 16 | process.exit() 17 | } 18 | 19 | setTimeout(() => {}, 50000) 20 | 21 | const repo = new Repo({ path, storage: raf }) 22 | repo.meta(id,(meta) => { 23 | console.log(meta) 24 | process.exit() 25 | }) 26 | -------------------------------------------------------------------------------- /src/JsonBuffer.ts: -------------------------------------------------------------------------------- 1 | export function parse(buffer: ArrayBuffer | ArrayBufferView): any { 2 | // const decoder = new TextDecoder() 3 | return JSON.parse(buffer.toString()) 4 | } 5 | 6 | export function bufferify(value: any): Buffer { 7 | // const encoder = new TextEncoder() 8 | return Buffer.from(JSON.stringify(value)) 9 | } 10 | 11 | export function parseAllValid(buffers: Uint8Array[]): any[] { 12 | const out = [] 13 | for (let i = 0; i < buffers.length; i++) { 14 | try { 15 | out.push(parse(buffers[i])) 16 | } catch (e) { 17 | console.warn(`Found invalid JSON in buffer ${i}`, e) 18 | continue 19 | } 20 | } 21 | return out 22 | } 23 | -------------------------------------------------------------------------------- /dist/SqlDatabase.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"SqlDatabase.js","sourceRoot":"","sources":["../src/SqlDatabase.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAuB;AACvB,oDAA2B;AAC3B,oEAAkD;AAClD,4CAAmB;AAGnB,MAAM,GAAG,GAAG,eAAK,CAAC,aAAa,CAAC,CAAA;AAEhC,MAAM,cAAc,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,sCAAsC,CAAC,CAAA;AAEtF,SAAgB,IAAI,CAAC,OAAe,EAAE,MAAe;IACnD,MAAM,EAAE,GAAG,IAAI,wBAAO,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,CAAC,CAAA;IACX,OAAO,EAAE,CAAA;AACX,CAAC;AAJD,oBAIC;AAED,SAAS,OAAO,CAAC,EAAoB;IACnC,GAAG,CAAC,cAAc,CAAC,CAAA;IACnB,MAAM,SAAS,GAAG,YAAE,CAAC,YAAY,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAA;IACxE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAClB,GAAG,CAAC,oBAAoB,CAAC,CAAA;AAC3B,CAAC"} -------------------------------------------------------------------------------- /examples/chat/src/diffy.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'diffy' { 2 | function DiffyConstructor(options?: Options): Diffy 3 | export default DiffyConstructor 4 | export interface Options { 5 | fullscreen?: boolean 6 | } 7 | 8 | export interface Diffy { 9 | render(cb: () => string) 10 | width: number 11 | height: number 12 | } 13 | } 14 | 15 | declare module 'diffy/input' { 16 | function DiffyConstructor(options?: InputOptions): DiffyInput 17 | export default DiffyConstructor 18 | 19 | export interface InputOptions { 20 | showCursor?: boolean 21 | } 22 | export interface DiffyInput { 23 | on(event: string, cb: any): void 24 | line(): string 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/JsonBuffer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"JsonBuffer.js","sourceRoot":"","sources":["../src/JsonBuffer.ts"],"names":[],"mappings":";;;AAAA,SAAgB,KAAK,CAAC,MAAqC;IACzD,oCAAoC;IACpC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;AACtC,CAAC;AAHD,sBAGC;AAED,SAAgB,SAAS,CAAC,KAAU;IAClC,oCAAoC;IACpC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;AAC3C,CAAC;AAHD,8BAGC;AAED,SAAgB,aAAa,CAAC,OAAqB;IACjD,MAAM,GAAG,GAAG,EAAE,CAAA;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACvC,IAAI;YACF,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;SAC5B;QAAC,OAAO,CAAC,EAAE;YACV,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;YACpD,SAAQ;SACT;KACF;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAXD,sCAWC"} -------------------------------------------------------------------------------- /examples/simple-repos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-repos", 3 | "version": "0.0.1", 4 | "description": "Simple test app for playing wiht Repos", 5 | "main": "dist/index.js", 6 | "author": "John Toohey", 7 | "scripts": { 8 | "test": "ts-node src/main.ts" 9 | }, 10 | "dependencies": { 11 | "@types/bs58": "^4.0.1", 12 | "hypermerge": "github:automerge/hypermerge" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^12.12.6", 16 | "@types/proper-lockfile": "^4.1.1", 17 | "@types/tape": "^4.2.32", 18 | "@types/uuid": "^3.4.5", 19 | "hyperswarm": "^2.3.1", 20 | "prettier": "^1.19.1", 21 | "tape": "^4.11.0", 22 | "ts-node": "^8.3.0", 23 | "typescript": "^3.7.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SqlDatabase.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import Debug from './Debug' 3 | import sqlite3, { Database } from 'better-sqlite3' 4 | import fs from 'fs' 5 | export { Database, Statement } from 'better-sqlite3' 6 | 7 | const log = Debug('SqlDatabase') 8 | 9 | const migrationsPath = path.resolve(__dirname, './migrations/0001_initial_schema.sql') 10 | 11 | export function open(storage: string, memory: boolean): Database { 12 | const db = new sqlite3(storage, { memory }) 13 | migrate(db) 14 | return db 15 | } 16 | 17 | function migrate(db: sqlite3.Database): void { 18 | log('migrating...') 19 | const migration = fs.readFileSync(migrationsPath, { encoding: 'utf-8' }) 20 | db.exec(migration) 21 | log('migration complete') 22 | } 23 | -------------------------------------------------------------------------------- /src/types/noise-peer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'noise-peer' { 2 | import { Duplex } from 'stream' 3 | 4 | const noisePeer: NoisePeer 5 | export default noisePeer 6 | // export function keyGen(): 7 | 8 | export interface NoisePeer { 9 | (rawStream: Duplex, isInitiator: boolean, noiseOpts?: NoiseOptions): SecureStream 10 | } 11 | 12 | interface SecureStream extends Duplex { 13 | /** 14 | * Boolean indicating whether the stream is a client/initiator or a server/responder, 15 | * as given by the isInitiator constructor argument. 16 | */ 17 | initiator: boolean 18 | 19 | /** Access to the rawStream passed in the constructor */ 20 | rawStream: Duplex 21 | } 22 | 23 | interface NoiseOptions {} 24 | } 25 | -------------------------------------------------------------------------------- /dist/MessageRouter.d.ts: -------------------------------------------------------------------------------- 1 | import PeerConnection from './PeerConnection'; 2 | import MessageBus from './MessageBus'; 3 | import NetworkPeer from './NetworkPeer'; 4 | import Queue from './Queue'; 5 | export interface Routed { 6 | sender: NetworkPeer; 7 | channelName: string; 8 | msg: Msg; 9 | } 10 | export default class MessageRouter { 11 | channelName: string; 12 | buses: WeakMap>; 13 | inboxQ: Queue>; 14 | constructor(channelName: string); 15 | listenTo(peer: NetworkPeer): void; 16 | sendToPeers(peers: Iterable, msg: Msg): void; 17 | sendToPeer(peer: NetworkPeer, msg: Msg): void; 18 | getBus(peer: NetworkPeer): MessageBus | undefined; 19 | } 20 | -------------------------------------------------------------------------------- /tools/Peek.ts: -------------------------------------------------------------------------------- 1 | 2 | import fs from "fs" 3 | import { Repo } from "../src" 4 | const raf: Function = require("random-access-file") 5 | const path = process.argv[2] 6 | const id = process.argv[3] 7 | 8 | if (path === undefined || id === undefined) { 9 | console.log("Usage: peek REPO_DIR DOC_ID") 10 | process.exit() 11 | } 12 | 13 | if (!fs.existsSync(path + "/ledger")) { 14 | console.log("No repo found: " + path) 15 | process.exit() 16 | } 17 | 18 | if (!fs.existsSync(path + "/" + id)) { 19 | console.log("No doc found in repo: " + id) 20 | process.exit() 21 | } 22 | 23 | setTimeout(() => {}, 50000) 24 | 25 | const repo = new Repo({ path, storage: raf }) 26 | repo.doc(id).then((doc) => { 27 | console.log(doc) 28 | process.exit() 29 | }) 30 | -------------------------------------------------------------------------------- /tests/NetworkPeer.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { testPeerPair } from './misc' 3 | 4 | test('NetworkPeer', (t) => { 5 | const [peerA, peerB] = testPeerPair() 6 | 7 | t.test('peer connections', async (t) => { 8 | t.plan(4) 9 | 10 | peerA.connectionQ.subscribe((conn) => { 11 | t.assert(peerA.connection === conn, 'peerA gets confirmed connection') 12 | t.assert(peerA.isConnected, 'peerA is connected') 13 | }) 14 | 15 | peerB.connectionQ.subscribe((conn) => { 16 | t.assert(peerB.connection === conn, 'peerB gets confirmed connection') 17 | t.assert(peerB.isConnected, 'peerB is connected') 18 | }) 19 | }) 20 | 21 | test.onFinish(() => { 22 | peerA.close() 23 | peerB.close() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /dist/FileStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { HyperfileUrl } from './Misc'; 3 | import { Readable } from 'stream'; 4 | import FeedStore from './FeedStore'; 5 | import Queue from './Queue'; 6 | export declare const BLOCK_SIZE: number; 7 | export interface Header { 8 | url: HyperfileUrl; 9 | size: number; 10 | blocks: number; 11 | mimeType: string; 12 | sha256: string; 13 | } 14 | export default class FileStore { 15 | private feeds; 16 | writeLog: Queue
; 17 | constructor(store: FeedStore); 18 | header(url: HyperfileUrl): Promise
; 19 | read(url: HyperfileUrl): Promise; 20 | write(stream: Readable, mimeType: string): Promise
; 21 | } 22 | export declare function isHyperfileUrl(url: string): url is HyperfileUrl; 23 | -------------------------------------------------------------------------------- /dist/Heartbeat.d.ts: -------------------------------------------------------------------------------- 1 | export interface Handlers { 2 | onBeat: () => void; 3 | onTimeout: () => void; 4 | } 5 | export default class Heartbeat { 6 | ms: number; 7 | interval: Interval; 8 | timeout: Timeout; 9 | beating: boolean; 10 | constructor(ms: number, { onBeat, onTimeout }: Handlers); 11 | start(): this; 12 | stop(): this; 13 | bump(): void; 14 | } 15 | export declare class Interval { 16 | ms: number; 17 | onInterval: () => void; 18 | constructor(ms: number, onInterval: () => void); 19 | start(): void; 20 | stop(): void; 21 | } 22 | export declare class Timeout { 23 | ms: number; 24 | onTimeout: () => void; 25 | constructor(ms: number, onTimeout: () => void); 26 | start(): void; 27 | stop(): void; 28 | bump(): void; 29 | } 30 | -------------------------------------------------------------------------------- /src/migrations/0001_initial_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Clocks ( 2 | repoId TEXT NOT NULL, 3 | documentId TEXT NOT NULL, 4 | actorId TEXT NOT NULL, 5 | seq INTEGER NOT NULL, 6 | PRIMARY KEY (repoId, documentId, actorId) 7 | ) WITHOUT ROWID; 8 | 9 | CREATE TABLE IF NOT EXISTS Keys ( 10 | name TEXT PRIMARY KEY, 11 | publicKey BLOB NOT NULL, 12 | secretKey BLOB 13 | ) WITHOUT ROWID; 14 | 15 | CREATE TABLE IF NOT EXISTS Cursors ( 16 | repoId TEXT NOT NULL, 17 | documentId TEXT NOT NULL, 18 | actorId TEXT NOT NULL, 19 | seq INTEGER NOT NULL, 20 | PRIMARY KEY (repoId, documentId, actorId) 21 | ) WITHOUT ROWID; 22 | 23 | CREATE TABLE IF NOT EXISTS Feeds ( 24 | discoveryId TEXT PRIMARY KEY, 25 | publicId TEXT NOT NULL UNIQUE, 26 | isWritable BOOLEAN NOT NULL 27 | ) WITHOUT ROWID; 28 | -------------------------------------------------------------------------------- /dist/migrations/0001_initial_schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Clocks ( 2 | repoId TEXT NOT NULL, 3 | documentId TEXT NOT NULL, 4 | actorId TEXT NOT NULL, 5 | seq INTEGER NOT NULL, 6 | PRIMARY KEY (repoId, documentId, actorId) 7 | ) WITHOUT ROWID; 8 | 9 | CREATE TABLE IF NOT EXISTS Keys ( 10 | name TEXT PRIMARY KEY, 11 | publicKey BLOB NOT NULL, 12 | secretKey BLOB 13 | ) WITHOUT ROWID; 14 | 15 | CREATE TABLE IF NOT EXISTS Cursors ( 16 | repoId TEXT NOT NULL, 17 | documentId TEXT NOT NULL, 18 | actorId TEXT NOT NULL, 19 | seq INTEGER NOT NULL, 20 | PRIMARY KEY (repoId, documentId, actorId) 21 | ) WITHOUT ROWID; 22 | 23 | CREATE TABLE IF NOT EXISTS Feeds ( 24 | discoveryId TEXT PRIMARY KEY, 25 | publicId TEXT NOT NULL UNIQUE, 26 | isWritable BOOLEAN NOT NULL 27 | ) WITHOUT ROWID; 28 | -------------------------------------------------------------------------------- /dist/KeyStore.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"KeyStore.js","sourceRoot":"","sources":["../src/KeyStore.ts"],"names":[],"mappings":";;AASA,MAAqB,QAAQ;IAM3B,YAAY,EAAY;QACtB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;QAEZ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAA;QACrE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;oGAE6D,CAAC,CAAA;QACjG,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAA;IACvE,CAAC;IAED,GAAG,CAAC,IAAY;QACd,MAAM,GAAG,GAAuB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC1D,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACjF,CAAC;IAED,GAAG,CAAC,IAAY,EAAE,OAAuB;QACvC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAChE,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,IAAY;QAChB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;CACF;AA7BD,2BA6BC"} -------------------------------------------------------------------------------- /tools/Serve.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { Repo } from '../src' 3 | const raf: Function = require('random-access-file') 4 | const ram: Function = require('random-access-memory') 5 | const id = process.argv[2] 6 | const _path = process.argv[3] 7 | const path = _path || '.data' 8 | 9 | if (id === undefined) { 10 | console.log('Usage: watch DOC_ID [REPO]') 11 | process.exit() 12 | } 13 | 14 | const storage = raf 15 | const repo = new Repo({ path, storage }) 16 | 17 | const url = 'wss://discovery-cloud.herokuapp.com' 18 | 19 | repo.meta(id, (meta) => { 20 | console.log('META', meta) 21 | if (meta && meta.type === 'File') { 22 | repo.readFile(id, (file, mimeType) => { 23 | console.log('FILE', file.length, mimeType) 24 | }) 25 | } else { 26 | repo.watch(id, (val, c) => { 27 | console.log('CLOCK', c) 28 | console.log(val) 29 | }) 30 | } 31 | }) 32 | -------------------------------------------------------------------------------- /dist/JsonBuffer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.parseAllValid = exports.bufferify = exports.parse = void 0; 4 | function parse(buffer) { 5 | // const decoder = new TextDecoder() 6 | return JSON.parse(buffer.toString()); 7 | } 8 | exports.parse = parse; 9 | function bufferify(value) { 10 | // const encoder = new TextEncoder() 11 | return Buffer.from(JSON.stringify(value)); 12 | } 13 | exports.bufferify = bufferify; 14 | function parseAllValid(buffers) { 15 | const out = []; 16 | for (let i = 0; i < buffers.length; i++) { 17 | try { 18 | out.push(parse(buffers[i])); 19 | } 20 | catch (e) { 21 | console.warn(`Found invalid JSON in buffer ${i}`, e); 22 | continue; 23 | } 24 | } 25 | return out; 26 | } 27 | exports.parseAllValid = parseAllValid; 28 | //# sourceMappingURL=JsonBuffer.js.map -------------------------------------------------------------------------------- /dist/Keys.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Keys.js","sourceRoot":"","sources":["../src/Keys.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA8B;AAC9B,yDAA0C;AAC1C,uDAAwF;AAGtC,6FAHA,+BAAY,OAGA;AAiB9D,SAAgB,MAAM;IACpB,OAAO,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;AACrC,CAAC;AAFD,wBAEC;AAED,SAAgB,YAAY;IAC1B,OAAO,MAAM,CAAC,OAAO,EAAE,CAAA;AACzB,CAAC;AAFD,oCAEC;AAED,SAAgB,UAAU,CAAC,IAAa;IACtC,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QACjC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;KAC/D,CAAA;AACH,CAAC;AALD,gCAKC;AAID,SAAgB,UAAU,CAAC,IAAe;IACxC,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;QACjC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;KAC/D,CAAA;AACH,CAAC;AALD,gCAKC;AAMD,SAAgB,MAAM,CAAC,GAAW;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;AAC3B,CAAC;AAFD,wBAEC;AAMD,SAAgB,MAAM,CAAC,GAAW;IAChC,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;AAC3B,CAAC;AAFD,wBAEC"} -------------------------------------------------------------------------------- /examples/chat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hm-chat", 3 | "version": "1.0.8", 4 | "description": "Simple chat client using hypermerge", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "chat": "ts-node src/chat.ts" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "@types/prompt-sync": "^4.1.0", 12 | "diffy": "^2.1.0", 13 | "hypermerge": "github:automerge/hypermerge", 14 | "minimist": "^1.2.0", 15 | "prompt-sync": "^4.1.5", 16 | "strip-ansi": "5.2.0" 17 | }, 18 | "devDependencies": { 19 | "@types/bs58": "^4.0.1", 20 | "@types/minimist": "^1.2.0", 21 | "@types/node": "^12.12.6", 22 | "@types/proper-lockfile": "^4.1.1", 23 | "@types/strip-ansi": "^5.2.1", 24 | "@types/tape": "^4.2.32", 25 | "@types/uuid": "^3.4.5", 26 | "hyperswarm": "^2.3.1", 27 | "prettier": "^1.19.1", 28 | "tape": "^4.11.0", 29 | "ts-node": "^8.3.0", 30 | "typescript": "^3.7.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SwarmInterface.ts: -------------------------------------------------------------------------------- 1 | import { Duplex } from 'stream' 2 | 3 | export type SocketType = string 4 | 5 | export interface Swarm { 6 | join(dk: Buffer, options?: JoinOptions): void 7 | leave(dk: Buffer): void 8 | on(name: K, cb: SwarmEvents[K]): this 9 | off(name: K, cb: SwarmEvents[K]): this 10 | destroy(cb: () => void): void 11 | } 12 | 13 | export interface SwarmEvents { 14 | connection(socket: Duplex, details: ConnectionDetails): void 15 | peer(peer: PeerInfo): void 16 | } 17 | 18 | export interface JoinOptions { 19 | announce?: boolean 20 | lookup?: boolean 21 | } 22 | 23 | export interface ConnectionDetails { 24 | type: SocketType 25 | reconnect?(shouldReconnect: boolean): void 26 | ban?(): void 27 | client: boolean 28 | peer: PeerInfo | null 29 | } 30 | 31 | export interface PeerInfo { 32 | port: number 33 | host: string // IP of peer 34 | local: boolean // Is the peer on the LAN? 35 | } 36 | -------------------------------------------------------------------------------- /dist/SwarmInterface.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Duplex } from 'stream'; 3 | export declare type SocketType = string; 4 | export interface Swarm { 5 | join(dk: Buffer, options?: JoinOptions): void; 6 | leave(dk: Buffer): void; 7 | on(name: K, cb: SwarmEvents[K]): this; 8 | off(name: K, cb: SwarmEvents[K]): this; 9 | destroy(cb: () => void): void; 10 | } 11 | export interface SwarmEvents { 12 | connection(socket: Duplex, details: ConnectionDetails): void; 13 | peer(peer: PeerInfo): void; 14 | } 15 | export interface JoinOptions { 16 | announce?: boolean; 17 | lookup?: boolean; 18 | } 19 | export interface ConnectionDetails { 20 | type: SocketType; 21 | reconnect?(shouldReconnect: boolean): void; 22 | ban?(): void; 23 | client: boolean; 24 | peer: PeerInfo | null; 25 | } 26 | export interface PeerInfo { 27 | port: number; 28 | host: string; 29 | local: boolean; 30 | } 31 | -------------------------------------------------------------------------------- /dist/KeyStore.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class KeyStore { 4 | constructor(db) { 5 | this.db = db; 6 | this.preparedGet = this.db.prepare(`SELECT * FROM Keys WHERE name=?`); 7 | this.preparedSet = this.db.prepare(` 8 | INSERT INTO Keys (name, publicKey, secretKey) VALUES (?, ?, ?) 9 | ON CONFLICT (name) DO UPDATE SET publicKey=excluded.publicKey, secretKey=excluded.secretKey`); 10 | this.preparedClear = this.db.prepare(`DELETE FROM Keys WHERE name=?`); 11 | } 12 | get(name) { 13 | const res = this.preparedGet.get(name); 14 | return res ? { publicKey: res.publicKey, secretKey: res.secretKey } : undefined; 15 | } 16 | set(name, keyPair) { 17 | this.preparedSet.run(name, keyPair.publicKey, keyPair.secretKey); 18 | return keyPair; 19 | } 20 | clear(name) { 21 | this.preparedClear.run(name); 22 | } 23 | } 24 | exports.default = KeyStore; 25 | //# sourceMappingURL=KeyStore.js.map -------------------------------------------------------------------------------- /dist/PeerConnection.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Duplex } from 'stream'; 3 | import { Channel } from './Multiplex'; 4 | import MessageBus from './MessageBus'; 5 | declare type CloseReason = 'outdated' | 'timeout' | 'error' | 'shutdown' | 'self-connection' | 'unknown'; 6 | export interface SocketInfo { 7 | type: string; 8 | isClient: boolean; 9 | } 10 | export default class PeerConnection { 11 | isClient: boolean; 12 | type: SocketInfo['type']; 13 | id?: string; 14 | onClose?: (reason: CloseReason) => void; 15 | private heartbeat; 16 | private rawSocket; 17 | private multiplex; 18 | private secureStream; 19 | private internalBus; 20 | constructor(rawSocket: Duplex, info: SocketInfo); 21 | get isOpen(): boolean; 22 | get isClosed(): boolean; 23 | openBus(name: string, subscriber?: (msg: M) => void): MessageBus; 24 | openChannel(name: string): Channel; 25 | close(reason?: CloseReason): void; 26 | private onMsg; 27 | private closeOutdated; 28 | private log; 29 | } 30 | export {}; 31 | -------------------------------------------------------------------------------- /dist/SqlDatabase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.open = void 0; 7 | const path_1 = __importDefault(require("path")); 8 | const Debug_1 = __importDefault(require("./Debug")); 9 | const better_sqlite3_1 = __importDefault(require("better-sqlite3")); 10 | const fs_1 = __importDefault(require("fs")); 11 | const log = Debug_1.default('SqlDatabase'); 12 | const migrationsPath = path_1.default.resolve(__dirname, './migrations/0001_initial_schema.sql'); 13 | function open(storage, memory) { 14 | const db = new better_sqlite3_1.default(storage, { memory }); 15 | migrate(db); 16 | return db; 17 | } 18 | exports.open = open; 19 | function migrate(db) { 20 | log('migrating...'); 21 | const migration = fs_1.default.readFileSync(migrationsPath, { encoding: 'utf-8' }); 22 | db.exec(migration); 23 | log('migration complete'); 24 | } 25 | //# sourceMappingURL=SqlDatabase.js.map -------------------------------------------------------------------------------- /dist/Clock.d.ts: -------------------------------------------------------------------------------- 1 | import { ActorId } from './Misc'; 2 | export interface Clock { 3 | [actorId: string]: number; 4 | } 5 | export declare type CMP = 'GT' | 'LT' | 'CONCUR' | 'EQ'; 6 | export declare function getMax(clocks: Clock[]): Clock | undefined; 7 | export declare function sequenceTotal(clock: Clock): number; 8 | export declare function isSatisfied(target: Clock, candidate: Clock): boolean; 9 | export declare function actors(clock: Clock): ActorId[]; 10 | export declare function gte(a: Clock, b: Clock): boolean; 11 | export declare function equal(a: Clock, b: Clock): boolean; 12 | export declare function cmp(a: Clock, b: Clock): CMP; 13 | export declare function strs2clock(input: string | string[]): Clock; 14 | export declare function clock2strs(clock: Clock): string[]; 15 | export declare function clockDebug(c: Clock): string; 16 | export declare function equivalent(c1: Clock, c2: Clock): boolean; 17 | export declare function union(c1: Clock, c2: Clock): Clock; 18 | export declare function addTo(acc: Clock, clock: Clock): void; 19 | export declare function intersection(c1: Clock, c2: Clock): Clock; 20 | -------------------------------------------------------------------------------- /src/types/hypercore-crypto.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'hypercore-crypto' { 2 | import sodium from 'sodium-native' 3 | export type Key = sodium.Key 4 | export type PublicKey = sodium.PublicSigningKey 5 | export type SecretKey = sodium.SecretSigningKey 6 | export type DiscoveryKey = Key & { __discoveryKey: true } 7 | export type KeyPair = sodium.SigningKeyPair 8 | 9 | /** Returns an ED25519 keypair that can used for tree signing. */ 10 | export function keyPair(): KeyPair 11 | 12 | /** Signs a message (buffer). */ 13 | export function sign(message: Buffer, secretKey: SecretKey): Buffer 14 | 15 | /** Verifies a signature for a message. */ 16 | export function verify(message: Buffer, signature: Buffer, publicKey: PublicKey): boolean 17 | 18 | /** Returns a buffer containing random bytes of size `size`. */ 19 | export function randomBytes(size: number): Buffer 20 | 21 | /** 22 | * Return a hash derived from a `publicKey` that can used for discovery without disclosing the 23 | * public key. 24 | */ 25 | export function discoveryKey(publicKey: PublicKey): DiscoveryKey 26 | } 27 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:12.4.0 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn tape 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Ink & Switch LLC, and University of Cambridge 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /dist/MessageRouter.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"MessageRouter.js","sourceRoot":"","sources":["../src/MessageRouter.ts"],"names":[],"mappings":";;;;;AAGA,oDAA2B;AAC3B,iCAAoC;AAQpC,MAAqB,aAAa;IAKhC,YAAY,WAAmB;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,KAAK,GAAG,IAAI,OAAO,EAAE,CAAA;QAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,eAAK,CAAC,sBAAsB,CAAC,CAAA;IACjD,CAAC;IAED,QAAQ,CAAC,IAAiB;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACnB,CAAC;IAED,WAAW,CAAC,KAA4B,EAAE,GAAQ;QAChD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;SAC3B;IACH,CAAC;IAED,UAAU,CAAC,IAAiB,EAAE,GAAQ;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC7B,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,IAAI,CAAC,GAAG,EAAC;IAChB,CAAC;IAED,MAAM,CAAC,IAAiB;QACtB,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAM;QAE5B,OAAO,kBAAW,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE;YACvD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAM,IAAI,CAAC,WAAW,CAAC,CAAA;YAE/C,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC7B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,IAAI;oBACZ,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,GAAG;iBACJ,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;YACF,OAAO,GAAG,CAAA;QACZ,CAAC,CAAC,CAAA;IACJ,CAAC;CACF;AA1CD,gCA0CC"} -------------------------------------------------------------------------------- /dist/CursorStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RepoId, DocId, ActorId } from './Misc'; 3 | import * as Clock from './Clock'; 4 | import { Database } from './SqlDatabase'; 5 | import Queue from './Queue'; 6 | export declare type Cursor = Clock.Clock; 7 | export declare type CursorEntry = [ActorId, number]; 8 | export declare type CursorDescriptor = [Cursor, DocId, RepoId]; 9 | export declare const INFINITY_SEQ: number; 10 | export default class CursorStore { 11 | private db; 12 | private preparedGet; 13 | private preparedInsert; 14 | private preparedEntry; 15 | private preparedDocsWithActor; 16 | private preparedAllDocumentIds; 17 | updateQ: Queue; 18 | constructor(db: Database); 19 | get(repoId: RepoId, docId: DocId): Cursor; 20 | update(repoId: RepoId, docId: DocId, cursor: Cursor): CursorDescriptor; 21 | entry(repoId: RepoId, docId: DocId, actorId: ActorId): number; 22 | docsWithActor(repoId: RepoId, actorId: ActorId, seq?: number): DocId[]; 23 | addActor(repoId: RepoId, docId: DocId, actorId: ActorId, seq?: number): CursorDescriptor; 24 | getAllDocumentIds(repoId: RepoId): DocId[]; 25 | } 26 | -------------------------------------------------------------------------------- /dist/ReplicationManager.d.ts: -------------------------------------------------------------------------------- 1 | import NetworkPeer from './NetworkPeer'; 2 | import FeedStore, { FeedId } from './FeedStore'; 3 | import { DiscoveryId } from './Misc'; 4 | import MessageRouter from './MessageRouter'; 5 | import MapSet from './MapSet'; 6 | import Queue from './Queue'; 7 | declare type ReplicationMsg = DiscoveryIdsMsg; 8 | interface DiscoveryIdsMsg { 9 | type: 'DiscoveryIds'; 10 | discoveryIds: DiscoveryId[]; 11 | } 12 | export interface Discovery { 13 | feedId: FeedId; 14 | discoveryId: DiscoveryId; 15 | peer: NetworkPeer; 16 | } 17 | export default class ReplicationManager { 18 | private protocols; 19 | private feeds; 20 | messages: MessageRouter; 21 | replicating: MapSet; 22 | discoveryQ: Queue; 23 | constructor(feeds: FeedStore); 24 | getPeersWith(discoveryIds: DiscoveryId[]): Set; 25 | close(): void; 26 | /** 27 | * Call this when a peer connects. 28 | */ 29 | onPeer: (peer: NetworkPeer) => void; 30 | private replicateWith; 31 | private onFeedCreated; 32 | private onMessage; 33 | private getOrCreateProtocol; 34 | } 35 | export {}; 36 | -------------------------------------------------------------------------------- /src/types/simple-message-channels.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'simple-message-channels' { 2 | export type Type = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 3 | 4 | export interface Options { 5 | onmessage(channel: number, type: Type, message: Buffer): void 6 | } 7 | 8 | export default class SMC { 9 | constructor(options: Options) 10 | 11 | /** 12 | * Encode a channel, type, message to be sent to another person. Channel can be any number 13 | * and type can be any 4 bit number. Message should be a buffer. 14 | */ 15 | send(channel: number, type: Type, message: Buffer): Buffer 16 | 17 | /** 18 | * Parse a payload buffer chunk. Once a full message has been parsed the 19 | * `smc.onmessage(channel, type, message)` handler is called. 20 | * Returns `true` if the chunk seemed valid and false if not. If false is returned check 21 | * `smc.error` to see the error it hit. 22 | */ 23 | recv(payloadChunk: Buffer): boolean 24 | 25 | error?: Error 26 | 27 | /** Encodes a series of messages into a single paylaod buffer. */ 28 | sendBatch(messages: { channel: number; type: Type; message: Buffer }[]): Buffer 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dist/Block.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Block.js","sourceRoot":"","sources":["../src/Block.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA4B;AAC5B,yDAA0C;AAE1C,MAAM,MAAM,GAAG,IAAI,CAAA;AAEnB,MAAM,EACJ,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GACrB,GAAG,IAAI,CAAC,SAAS,CAAA;AAElB,SAAgB,IAAI,CAAC,GAAW;IAC9B,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACvC,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IACxC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAC3B,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE;QAC9B,MAAM,EAAE;YACN,CAAC,iBAAiB,CAAC,EAAE,gBAAgB;YACrC,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM;YACvC,CAAC,oBAAoB,CAAC,EAAE,EAAE;SAC3B;KACF,CAAC,CACH,CAAA;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE;QACpC,OAAO,MAAM,CAAA;KACd;SAAM;QACL,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAA;KAC/C;AACH,CAAC;AAjBD,oBAiBC;AAED,SAAgB,MAAM,CAAC,IAAgB;IACrC,wFAAwF;IACxF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC/B,QAAQ,MAAM,CAAC,QAAQ,EAAE,EAAE;QACzB,KAAK,IAAI;YACP,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAE/B,KAAK,MAAM;YACT,OAAO,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QAEhF;YACE,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,GAAG,CAAC,CAAA;KACjE;AACH,CAAC;AAbD,wBAaC"} -------------------------------------------------------------------------------- /dist/TraverseLogic.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"TraverseLogic.js","sourceRoot":"","sources":["../src/TraverseLogic.ts"],"names":[],"mappings":";;;;;;AAAA,4EAA4E;AAC5E,0BAA0B;AAC1B,0DAAiC;AACjC,iCAAsC;AAEzB,QAAA,kBAAkB,GAAG,IAAI,CAAA;AAKtC,gFAAgF;AAChF,iEAAiE;AACjE,oCAAoC;AACpC,SAAgB,YAAY,CAAI,MAAgB,EAAE,IAAa;IAC7D,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAA;IACpB,MAAM,OAAO,GAAQ,EAAE,CAAA;IACvB,OAAO,KAAK,CAAC,MAAM,EAAE;QACnB,sDAAsD;QACtD,IAAI,KAAK,CAAC,MAAM,GAAG,0BAAkB,EAAE;YACrC,OAAO,CAAC,IAAI,CACV,iDAAiD,EACjD,eAAe,KAAK,CAAC,MAAM,EAAE,EAC7B,IAAI,CACL,CAAA;YACD,OAAO,OAAO,CAAA;SACf;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAA;QACvB,6EAA6E;QAC7E,8EAA8E;QAC9E,qCAAqC;QACrC,6EAA6E;QAC7E,IAAI,GAAG,YAAY,mBAAS,CAAC,IAAI,EAAE;YACjC,uCAAuC;YACvC,SAAQ;SACT;aAAM,IAAI,oBAAa,CAAC,GAAG,CAAC,EAAE;YAC7B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;SACnE;aAAM,IAAI,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE;YACjC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;SAC/C;aAAM,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE;YACtB,OAAO,CAAC,IAAI,CAAC,GAAQ,CAAC,CAAA;SACvB;KACF;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AA9BD,oCA8BC;AAOD,SAAS,UAAU,CAAC,GAAY;IAC9B,OAAO,CAAC,CAAE,GAAW,CAAC,OAAO,CAAA;AAC/B,CAAC"} -------------------------------------------------------------------------------- /dist/MessageBus.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"MessageBus.js","sourceRoot":"","sources":["../src/MessageBus.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA2B;AAC3B,yDAA0C;AAE1C,MAAqB,UAAU;IAK7B,YAAY,MAA8B,EAAE,UAA+B;QAkB3E,WAAM,GAAG,CAAC,IAAY,EAAQ,EAAE;YAC9B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;QAC5C,CAAC,CAAA;QAnBC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QAEpB,IAAI,CAAC,KAAK,GAAG,IAAI,eAAK,CAAC,kBAAkB,CAAC,CAAA;QAC1C,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAK,CAAC,qBAAqB,CAAC,CAAA;QAEhD,IAAI,CAAC,MAAM;aACR,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC;aACvB,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;aACjC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QAEpC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;YAC3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9C,CAAC,CAAC,CAAA;QAEF,IAAI,UAAU;YAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IAC5C,CAAC;IAMD,IAAI,CAAC,GAAQ;QACX,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC;IAED,SAAS,CAAC,KAAyB;QACjC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IAED,WAAW;QACT,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;IAC7B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;QACxB,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;QAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA;IACnB,CAAC;CACF;AA5CD,6BA4CC"} -------------------------------------------------------------------------------- /dist/Network.d.ts: -------------------------------------------------------------------------------- 1 | import { DiscoveryId } from './Misc'; 2 | import NetworkPeer, { PeerId } from './NetworkPeer'; 3 | import { Swarm, JoinOptions } from './SwarmInterface'; 4 | import Queue from './Queue'; 5 | export declare type NetworkMsg = InfoMsg; 6 | export interface InfoMsg { 7 | type: 'Info'; 8 | peerId: PeerId; 9 | } 10 | export default class Network { 11 | selfId: PeerId; 12 | joined: Set; 13 | peers: Map; 14 | peerQ: Queue; 15 | discovered: Set; 16 | swarms: Map; 17 | constructor(selfId: PeerId); 18 | join(discoveryId: DiscoveryId): void; 19 | leave(discoveryId: DiscoveryId): void; 20 | /** @deprecated */ 21 | get swarm(): Swarm | undefined; 22 | /** 23 | * @deprecated Use `addSwarm` 24 | */ 25 | setSwarm(swarm: Swarm, joinOptions?: JoinOptions): void; 26 | addSwarm(swarm: Swarm, joinOptions?: JoinOptions): void; 27 | removeSwarm(swarm: Swarm): void; 28 | get closedConnectionCount(): number; 29 | close(): Promise; 30 | getOrCreatePeer(peerId: PeerId): NetworkPeer; 31 | private closeSwarm; 32 | private swarmJoin; 33 | private swarmLeave; 34 | private onDiscovery; 35 | private onConnection; 36 | } 37 | -------------------------------------------------------------------------------- /src/MessageBus.ts: -------------------------------------------------------------------------------- 1 | import Queue from './Queue' 2 | import * as JsonBuffer from './JsonBuffer' 3 | 4 | export default class MessageBus { 5 | stream: NodeJS.ReadWriteStream 6 | sendQ: Queue 7 | receiveQ: Queue 8 | 9 | constructor(stream: NodeJS.ReadWriteStream, subscriber?: (msg: Msg) => void) { 10 | this.stream = stream 11 | 12 | this.sendQ = new Queue('MessageBus:sendQ') 13 | this.receiveQ = new Queue('MessageBus:receiveQ') 14 | 15 | this.stream 16 | .on('data', this.onData) 17 | .once('close', () => this.close()) 18 | .once('error', () => this.close()) 19 | 20 | this.sendQ.subscribe((msg) => { 21 | this.stream.write(JsonBuffer.bufferify(msg)) 22 | }) 23 | 24 | if (subscriber) this.subscribe(subscriber) 25 | } 26 | 27 | onData = (data: Buffer): void => { 28 | this.receiveQ.push(JsonBuffer.parse(data)) 29 | } 30 | 31 | send(msg: Msg): void { 32 | this.sendQ.push(msg) 33 | } 34 | 35 | subscribe(onMsg: (msg: Msg) => void): void { 36 | this.receiveQ.subscribe(onMsg) 37 | } 38 | 39 | unsubscribe(): void { 40 | this.receiveQ.unsubscribe() 41 | } 42 | 43 | close(): void { 44 | this.sendQ.unsubscribe() 45 | this.receiveQ.unsubscribe() 46 | this.stream.end() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dist/Actor.d.ts: -------------------------------------------------------------------------------- 1 | import { Change } from 'automerge'; 2 | import { ActorId, DiscoveryId } from './Misc'; 3 | import * as Keys from './Keys'; 4 | import FeedStore, { Feed } from './FeedStore'; 5 | export declare type ActorMsg = ActorFeedReady | ActorSync | Download; 6 | interface ActorSync { 7 | type: 'ActorSync'; 8 | actor: Actor; 9 | } 10 | interface ActorFeedReady { 11 | type: 'ActorFeedReady'; 12 | actor: Actor; 13 | feed: Feed; 14 | writable: boolean; 15 | } 16 | interface Download { 17 | type: 'Download'; 18 | actor: Actor; 19 | time: number; 20 | size: number; 21 | index: number; 22 | } 23 | interface ActorConfig { 24 | keys: Keys.KeyBuffer; 25 | notify: (msg: ActorMsg) => void; 26 | store: FeedStore; 27 | } 28 | export declare class Actor { 29 | id: ActorId; 30 | dkString: DiscoveryId; 31 | changes: Change[]; 32 | private q; 33 | private notify; 34 | private store; 35 | constructor(config: ActorConfig); 36 | onReady: (cb: (actor: Actor) => void) => void; 37 | writeChange(change: Change): void; 38 | close(): Promise; 39 | private getOrCreateFeed; 40 | private onFeedReady; 41 | private onDownload; 42 | private onSync; 43 | private onClose; 44 | private parseBlock; 45 | } 46 | export {}; 47 | -------------------------------------------------------------------------------- /tools/Watch.ts: -------------------------------------------------------------------------------- 1 | 2 | import fs from "fs" 3 | import { Repo } from "../src" 4 | import Client from "discovery-cloud-client" 5 | const raf: Function = require("random-access-file") 6 | const ram: Function = require("random-access-memory") 7 | const DiscoverySwarm = require("discovery-swarm"); 8 | const defaults = require('dat-swarm-defaults') 9 | const id = process.argv[2] 10 | const _path = process.argv[3] 11 | const path = _path || ".data" 12 | 13 | if (id === undefined) { 14 | console.log("Usage: watch DOC_ID [REPO]") 15 | process.exit() 16 | } 17 | 18 | /* 19 | if (_path && !fs.existsSync(_path + "/ledger")) { 20 | console.log("No repo found: " + _path) 21 | process.exit() 22 | } 23 | */ 24 | 25 | const storage = _path ? raf : ram 26 | const repo = new Repo({ path, storage }) 27 | 28 | const url = "wss://discovery-cloud.herokuapp.com" 29 | 30 | const discovery = new Client({ url, id: repo.id, stream: repo.stream, }) 31 | //const discovery = new DiscoverySwarm(defaults({stream: repo.stream, id: repo.id })); 32 | 33 | repo.replicate(discovery); 34 | 35 | if (id.startsWith("hyperfile:")) { 36 | repo.readFile(id, (file,mimeType) => { 37 | console.log("FILE",file.length,mimeType) 38 | }) 39 | } else { 40 | repo.watch(id, (val,c) => { 41 | console.log("CLOCK",c) 42 | // console.log(val.length) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /tests/KeyStore.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import * as SqlDatabase from '../src/SqlDatabase' 3 | import * as Keys from '../src/Keys' 4 | import KeyStore from '../src/KeyStore' 5 | 6 | test('KeyStore', (t) => { 7 | t.test('read and write', async (t) => { 8 | t.plan(1) 9 | const db = SqlDatabase.open('test.db', true) 10 | const keyStore = new KeyStore(db) 11 | const keys = Keys.createBuffer() 12 | keyStore.set('foo', keys) 13 | const result = keyStore.get('foo') 14 | t.deepEqual(result, keys) 15 | db.close() 16 | }) 17 | 18 | t.test('overwrite', async (t) => { 19 | t.plan(1) 20 | const db = SqlDatabase.open('test.db', true) 21 | const keyStore = new KeyStore(db) 22 | const keys = Keys.createBuffer() 23 | keyStore.set('foo', keys) 24 | const keys2 = Keys.createBuffer() 25 | keyStore.set('foo', keys2) 26 | const result = keyStore.get('foo') 27 | t.deepEqual(result, keys2) 28 | db.close() 29 | }) 30 | 31 | t.test('clear', async (t) => { 32 | t.plan(1) 33 | const db = SqlDatabase.open('test.db', true) 34 | const keyStore = new KeyStore(db) 35 | const keys = Keys.createBuffer() 36 | keyStore.set('foo', keys) 37 | keyStore.clear('foo') 38 | const result = keyStore.get('foo') 39 | t.equal(result, undefined) 40 | db.close() 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /tests/CursorStore.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import * as SqlDatabase from '../src/SqlDatabase' 3 | import CursorStore, { INFINITY_SEQ } from '../src/CursorStore' 4 | import { RepoId, DocId } from '../src/Misc' 5 | 6 | test('CursorStore', (t) => { 7 | t.test('read and write', async (t) => { 8 | t.plan(1) 9 | const repoId = 'repoId' as RepoId 10 | const db = SqlDatabase.open('test.db', true) 11 | const cursorStore = new CursorStore(db) 12 | 13 | const docId = 'abc123' as DocId 14 | const clock = { abc123: Infinity, def456: 0 } 15 | cursorStore.update(repoId, docId, clock) 16 | const readClock = cursorStore.get(repoId, docId) 17 | t.deepEqual(readClock, { abc123: INFINITY_SEQ, def456: 0 }) 18 | 19 | db.close() 20 | }) 21 | 22 | t.test('upsert', async (t) => { 23 | t.plan(1) 24 | const db = SqlDatabase.open('test.db', true) 25 | const cursorStore = new CursorStore(db) 26 | 27 | const repoId = 'repoId' as RepoId 28 | const docId = 'abc123' as DocId 29 | const cursor = { abc123: 1, def456: 0 } 30 | cursorStore.update(repoId, docId, cursor) 31 | const updatedCursor = { abc123: 2, def456: 0 } 32 | cursorStore.update(repoId, docId, updatedCursor) 33 | const readClock = cursorStore.get(repoId, docId) 34 | t.deepEqual(readClock, updatedCursor) 35 | db.close() 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/Block.ts: -------------------------------------------------------------------------------- 1 | import * as zlib from 'zlib' 2 | import * as JsonBuffer from './JsonBuffer' 3 | 4 | const BROTLI = 'BR' 5 | 6 | const { 7 | BROTLI_PARAM_MODE, 8 | BROTLI_MODE_TEXT, 9 | BROTLI_PARAM_SIZE_HINT, 10 | BROTLI_PARAM_QUALITY, 11 | } = zlib.constants 12 | 13 | export function pack(obj: Object): Buffer { 14 | const blockHeader = Buffer.from(BROTLI) 15 | const source = JsonBuffer.bufferify(obj) 16 | const blockBody = Buffer.from( 17 | zlib.brotliCompressSync(source, { 18 | params: { 19 | [BROTLI_PARAM_MODE]: BROTLI_MODE_TEXT, 20 | [BROTLI_PARAM_SIZE_HINT]: source.length, 21 | [BROTLI_PARAM_QUALITY]: 11, // 11 is default 22 | }, 23 | }) 24 | ) 25 | if (source.length < blockBody.length) { 26 | return source 27 | } else { 28 | return Buffer.concat([blockHeader, blockBody]) 29 | } 30 | } 31 | 32 | export function unpack(data: Uint8Array): any { 33 | //if (data.slice(0,2).toString() === '{"') { // an old block before we added compression 34 | const header = data.slice(0, 2) 35 | switch (header.toString()) { 36 | case '{"': 37 | return JsonBuffer.parse(data) 38 | 39 | case BROTLI: 40 | return JsonBuffer.parse(Buffer.from(zlib.brotliDecompressSync(data.slice(2)))) 41 | 42 | default: 43 | throw new Error(`fail to unpack blocks - head is '${header}'`) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/KeyStore.ts: -------------------------------------------------------------------------------- 1 | import { Database, Statement } from './SqlDatabase' 2 | import * as Keys from './Keys' 3 | 4 | interface KeyRow { 5 | name: string 6 | publicKey: Keys.PublicKey 7 | secretKey: Keys.SecretKey 8 | } 9 | 10 | export default class KeyStore { 11 | private db: Database 12 | private preparedGet: Statement 13 | private preparedSet: Statement<[string, Keys.PublicKey, Keys.SecretKey?]> 14 | private preparedClear: Statement 15 | 16 | constructor(db: Database) { 17 | this.db = db 18 | 19 | this.preparedGet = this.db.prepare(`SELECT * FROM Keys WHERE name=?`) 20 | this.preparedSet = this.db.prepare(` 21 | INSERT INTO Keys (name, publicKey, secretKey) VALUES (?, ?, ?) 22 | ON CONFLICT (name) DO UPDATE SET publicKey=excluded.publicKey, secretKey=excluded.secretKey`) 23 | this.preparedClear = this.db.prepare(`DELETE FROM Keys WHERE name=?`) 24 | } 25 | 26 | get(name: string): Keys.KeyBuffer | undefined { 27 | const res: KeyRow | undefined = this.preparedGet.get(name) 28 | return res ? { publicKey: res.publicKey, secretKey: res.secretKey } : undefined 29 | } 30 | 31 | set(name: string, keyPair: Keys.KeyBuffer): Keys.KeyBuffer { 32 | this.preparedSet.run(name, keyPair.publicKey, keyPair.secretKey) 33 | return keyPair 34 | } 35 | 36 | clear(name: string) { 37 | this.preparedClear.run(name) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dist/Handle.d.ts: -------------------------------------------------------------------------------- 1 | import { Clock, Doc, ChangeFn } from 'automerge'; 2 | import { RepoFrontend, ProgressEvent } from './RepoFrontend'; 3 | import { DocUrl } from './Misc'; 4 | export declare class Handle { 5 | url: DocUrl; 6 | state: Doc | null; 7 | clock: Clock | null; 8 | subscription?: (item: Doc, clock?: Clock, index?: number) => void; 9 | progressSubscription?: (event: ProgressEvent) => void; 10 | messageSubscription?: (event: any) => void; 11 | private counter; 12 | private repo; 13 | constructor(repo: RepoFrontend, url: DocUrl); 14 | fork(): DocUrl; 15 | merge(other: Handle): this; 16 | message: (contents: any) => this; 17 | push: (item: Doc, clock: Clock) => void; 18 | receiveProgressEvent: (progress: ProgressEvent) => void; 19 | receiveDocumentMessage: (contents: any) => void; 20 | once: (subscriber: (doc: Doc, clock?: Clock | undefined, index?: number | undefined) => void) => this; 21 | subscribe: (subscriber: (doc: Doc, clock?: Clock | undefined, index?: number | undefined) => void) => this; 22 | subscribeProgress: (subscriber: (event: ProgressEvent) => void) => this; 23 | subscribeMessage: (subscriber: (event: any) => void) => this; 24 | close: () => void; 25 | debug(): void; 26 | cleanup: () => void; 27 | changeFn: (_fn: ChangeFn) => void; 28 | change: (fn: ChangeFn) => this; 29 | } 30 | -------------------------------------------------------------------------------- /dist/DocFrontend.d.ts: -------------------------------------------------------------------------------- 1 | import { Patch, ChangeFn } from 'automerge'; 2 | import { RepoFrontend, ProgressEvent } from './RepoFrontend'; 3 | import { Clock } from './Clock'; 4 | import { Handle } from './Handle'; 5 | import { ActorId, DocId } from './Misc'; 6 | export { Patch }; 7 | interface Config { 8 | docId: DocId; 9 | actorId?: ActorId; 10 | } 11 | export declare class DocFrontend { 12 | private docId; 13 | private docUrl; 14 | ready: boolean; 15 | actorId?: ActorId; 16 | history: number; 17 | private changeQ; 18 | private front; 19 | private mode; 20 | private handles; 21 | private repo; 22 | clock: Clock; 23 | constructor(repo: RepoFrontend, config: Config); 24 | handle(): Handle; 25 | newState(): void; 26 | progress(progressEvent: ProgressEvent): void; 27 | messaged(contents: any): void; 28 | fork: () => string; 29 | change: (fn: ChangeFn) => void; 30 | release: () => void; 31 | setActorId: (actorId: ActorId) => void; 32 | init: (minimumClockSatisfied: boolean, actorId?: ActorId | undefined, patch?: Patch | undefined, history?: number | undefined) => void; 33 | private enableWrites; 34 | private updateClockChange; 35 | private updateClockPatch; 36 | patch: (patch: Patch, minimumClockSatisfied: boolean, history: number) => void; 37 | bench(msg: string, f: () => void): void; 38 | close(): void; 39 | } 40 | -------------------------------------------------------------------------------- /dist/StreamLogic.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Transform, TransformCallback, Readable } from 'stream'; 3 | import { Hash } from 'crypto'; 4 | export declare class ChunkSizeTransform extends Transform { 5 | private pending; 6 | chunkSize: number; 7 | processedBytes: number; 8 | chunkCount: number; 9 | constructor(chunkSize: number); 10 | _transform(data: Buffer, _encoding: string, cb: TransformCallback): void; 11 | _flush(cb: () => void): void; 12 | private pushChunks; 13 | private readPendingChunks; 14 | private pendingLength; 15 | } 16 | export declare class HashPassThrough extends Transform { 17 | readonly hash: Hash; 18 | constructor(algorithm: string); 19 | _transform(data: Buffer, _encoding: string, cb: TransformCallback): void; 20 | } 21 | export declare class PrefixMatchPassThrough extends Transform { 22 | prefix: Buffer; 23 | matched: boolean; 24 | constructor(prefix: Buffer); 25 | _transform(chunk: Buffer, _encoding: string, cb: TransformCallback): void; 26 | } 27 | export declare class InvalidPrefixError extends Error { 28 | actual: Buffer; 29 | expected: Buffer; 30 | constructor(actual: Buffer, expected: Buffer); 31 | } 32 | export declare function toBuffer(stream: Readable): Promise; 33 | export declare function fromBuffer(buffer: Buffer): Readable; 34 | export declare function fromBuffers(buffers: Buffer[]): Readable; 35 | -------------------------------------------------------------------------------- /dist/Repo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Repo = void 0; 4 | const RepoBackend_1 = require("./RepoBackend"); 5 | const RepoFrontend_1 = require("./RepoFrontend"); 6 | class Repo { 7 | constructor(opts) { 8 | this.back = new RepoBackend_1.RepoBackend(opts); 9 | this.front = new RepoFrontend_1.RepoFrontend(); 10 | this.front.subscribe(this.back.receive); 11 | this.back.subscribe(this.front.receive); 12 | this.id = this.back.id; 13 | this.create = this.front.create; 14 | this.open = this.front.open; 15 | this.message = this.front.message; 16 | this.destroy = this.front.destroy; 17 | this.meta = this.front.meta; 18 | this.doc = this.front.doc; 19 | this.fork = this.front.fork; 20 | this.close = this.front.close; 21 | this.change = this.front.change; 22 | this.files = this.front.files; 23 | this.watch = this.front.watch; 24 | this.merge = this.front.merge; 25 | this.setSwarm = this.back.setSwarm; 26 | this.addSwarm = this.back.addSwarm; 27 | this.removeSwarm = this.back.removeSwarm; 28 | this.startFileServer = this.back.startFileServer; 29 | this.materialize = this.front.materialize; 30 | this.crypto = this.front.crypto; 31 | } 32 | } 33 | exports.Repo = Repo; 34 | //# sourceMappingURL=Repo.js.map -------------------------------------------------------------------------------- /dist/CryptoClient.d.ts: -------------------------------------------------------------------------------- 1 | import * as Crypto from './Crypto'; 2 | import { DocUrl } from './Misc'; 3 | import { ToBackendQueryMsg } from './RepoMsg'; 4 | export declare type RequestFn = (msg: ToBackendQueryMsg, cb: (msg: any) => void) => void; 5 | export declare class CryptoClient { 6 | request: RequestFn; 7 | constructor(request: RequestFn); 8 | sign(url: DocUrl, message: T): Promise>; 9 | verify(url: DocUrl, signedMessage: Crypto.SignedMessage): Promise; 10 | /** 11 | * Helper function to extract the message from a SignedMessage. 12 | * Verifies the signature and returns the message if valid, otherwise rejects. 13 | */ 14 | verifiedMessage(url: DocUrl, signedMessage: Crypto.SignedMessage): Promise; 15 | box(senderSecretKey: Crypto.EncodedSecretEncryptionKey, recipientPublicKey: Crypto.EncodedPublicEncryptionKey, message: string): Promise; 16 | openBox(senderPublicKey: Crypto.EncodedPublicEncryptionKey, recipientSecretKey: Crypto.EncodedSecretEncryptionKey, box: Crypto.Box): Promise; 17 | sealedBox(publicKey: Crypto.EncodedPublicEncryptionKey, message: string): Promise; 18 | openSealedBox(keyPair: Crypto.EncodedEncryptionKeyPair, sealedBox: Crypto.EncodedSealedBoxCiphertext): Promise; 19 | encryptionKeyPair(): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /src/hypercore.ts: -------------------------------------------------------------------------------- 1 | import { Feed } from 'hypercore' 2 | 3 | import Debug from './Debug' 4 | import { ID, ActorId } from './Misc' 5 | const log = Debug('hypercore') 6 | 7 | function readFeedN( 8 | id: ActorId | 'ledger', 9 | feed: Feed, 10 | index: number, 11 | cb: (data: T[]) => void 12 | ) { 13 | log(`readFeedN id=${ID(id)} (0..${index})`) 14 | 15 | if (index === 0) { 16 | feed.get(0, { wait: false }, (err, data) => { 17 | if (err) log(`feed.get() error id=${ID(id)}`, err) 18 | if (err) throw err 19 | cb([data]) 20 | }) 21 | } else { 22 | feed.getBatch(0, index, { wait: false }, (err, data) => { 23 | if (err) log(`feed.getBatch error id=${ID(id)}`, err) 24 | if (err) throw err 25 | cb(data) 26 | }) 27 | } 28 | } 29 | 30 | export function readFeed(id: ActorId | 'ledger', feed: Feed, cb: (data: T[]) => void) { 31 | // const id = feed.id.toString('hex').slice(0,4) 32 | const length = feed.downloaded() 33 | 34 | log(`readFeed ${ID(id)} downloaded=${length} feed.length=${feed.length}`) 35 | 36 | if (length === 0) return cb([]) 37 | if (feed.has(0, length)) return readFeedN(id, feed, length, cb) 38 | 39 | for (let i = 0; i < length; i++) { 40 | if (!feed.has(i)) { 41 | feed.clear(i, feed.length, () => { 42 | log(`post clear -- readFeedN id=${ID(id)} n=${i - 1}`) 43 | readFeedN(id, feed, i - 1, cb) 44 | }) 45 | break 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dist/Repo.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Repo.js","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":";;;AAAA,+CAAoD;AACpD,iDAA6C;AAU7C,MAAa,IAAI;IA+Bf,YAAY,IAAa;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,yBAAW,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,2BAAY,EAAE,CAAA;QAC/B,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAA;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAA;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;QAC3B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAA;QAC7B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;QAC/B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAA;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAA;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAA;QAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAA;QAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAA;QAClC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAA;QACxC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAA;QAChD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAA;QACzC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IACjC,CAAC;CACF;AAxDD,oBAwDC"} -------------------------------------------------------------------------------- /src/MapSet.ts: -------------------------------------------------------------------------------- 1 | export default class MapSet { 2 | private map: Map> = new Map() 3 | 4 | add(key: A, val: B): boolean { 5 | return this.merge(key, [val]) 6 | } 7 | 8 | set(key: A, val: Set): void { 9 | this.map.set(key, val) 10 | } 11 | 12 | values(): Set[] { 13 | return [...this.map.values()] 14 | } 15 | 16 | union(): Set { 17 | const acc: B[] = [] 18 | for (const set of this.map.values()) { 19 | acc.push(...set) 20 | } 21 | return new Set(acc) 22 | } 23 | 24 | keys(): A[] { 25 | return [...this.map.keys()] 26 | } 27 | 28 | merge(key: A, vals: B[]): boolean { 29 | const current = this.get(key) 30 | const change = vals.some((val) => !current.has(val)) 31 | if (change) { 32 | this.map.set(key, new Set([...current, ...vals])) 33 | } 34 | return change 35 | } 36 | 37 | delete(key: A): Set { 38 | const old = this.get(key) 39 | this.map.delete(key) 40 | return old 41 | } 42 | 43 | remove(key: A, val: B) { 44 | this.get(key).delete(val) 45 | } 46 | 47 | keysWith(val: B): Set { 48 | const keys = new Set() 49 | this.map.forEach((vals: Set, key: A) => { 50 | if (vals.has(val)) { 51 | keys.add(key) 52 | } 53 | }) 54 | return keys 55 | } 56 | 57 | get(key: A): Set { 58 | return this.map.get(key) || new Set() 59 | } 60 | 61 | has(key: A, val: B): boolean { 62 | return this.get(key).has(val) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /dist/Keys.d.ts: -------------------------------------------------------------------------------- 1 | import { Key, PublicKey, SecretKey, DiscoveryKey, discoveryKey } from 'hypercore-crypto'; 2 | import * as Crypto from './Crypto'; 3 | export { Key, PublicKey, SecretKey, DiscoveryKey, discoveryKey }; 4 | export declare type KeyId = Crypto.EncodedPublicSigningKey; 5 | export declare type PublicId = KeyId & { 6 | __publicId: true; 7 | }; 8 | export declare type SecretId = KeyId & { 9 | __secretId: true; 10 | }; 11 | export declare type DiscoveryId = KeyId & { 12 | __discoveryId: true; 13 | }; 14 | export interface KeyBuffer { 15 | publicKey: PublicKey; 16 | secretKey?: SecretKey; 17 | } 18 | export interface KeyPair { 19 | publicKey: PublicId; 20 | secretKey?: SecretId; 21 | } 22 | export declare function create(): Required; 23 | export declare function createBuffer(): Required; 24 | export declare function decodePair(keys: KeyPair): KeyBuffer; 25 | export declare function encodePair(keys: Required): Required; 26 | export declare function encodePair(keys: KeyBuffer): KeyPair; 27 | export declare function decode(key: DiscoveryId): DiscoveryKey; 28 | export declare function decode(key: SecretId): SecretKey; 29 | export declare function decode(key: PublicId): PublicKey; 30 | export declare function decode(key: KeyId): Key; 31 | export declare function encode(key: DiscoveryKey): DiscoveryId; 32 | export declare function encode(key: SecretKey): SecretId; 33 | export declare function encode(key: PublicKey): PublicId; 34 | export declare function encode(key: Key): KeyId; 35 | -------------------------------------------------------------------------------- /dist/MessageRouter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Queue_1 = __importDefault(require("./Queue")); 7 | const Misc_1 = require("./Misc"); 8 | class MessageRouter { 9 | constructor(channelName) { 10 | this.channelName = channelName; 11 | this.buses = new WeakMap(); 12 | this.inboxQ = new Queue_1.default('MessageCenter:inboxQ'); 13 | } 14 | listenTo(peer) { 15 | this.getBus(peer); 16 | } 17 | sendToPeers(peers, msg) { 18 | for (const peer of peers) { 19 | this.sendToPeer(peer, msg); 20 | } 21 | } 22 | sendToPeer(peer, msg) { 23 | const bus = this.getBus(peer); 24 | bus === null || bus === void 0 ? void 0 : bus.send(msg); 25 | } 26 | getBus(peer) { 27 | if (!peer.connection) 28 | return; 29 | return Misc_1.getOrCreate(this.buses, peer.connection, (conn) => { 30 | const bus = conn.openBus(this.channelName); 31 | bus.receiveQ.subscribe((msg) => { 32 | this.inboxQ.push({ 33 | sender: peer, 34 | channelName: this.channelName, 35 | msg, 36 | }); 37 | }); 38 | return bus; 39 | }); 40 | } 41 | } 42 | exports.default = MessageRouter; 43 | //# sourceMappingURL=MessageRouter.js.map -------------------------------------------------------------------------------- /src/MessageRouter.ts: -------------------------------------------------------------------------------- 1 | import PeerConnection from './PeerConnection' 2 | import MessageBus from './MessageBus' 3 | import NetworkPeer from './NetworkPeer' 4 | import Queue from './Queue' 5 | import { getOrCreate } from './Misc' 6 | 7 | export interface Routed { 8 | sender: NetworkPeer 9 | channelName: string 10 | msg: Msg 11 | } 12 | 13 | export default class MessageRouter { 14 | channelName: string 15 | buses: WeakMap> 16 | inboxQ: Queue> 17 | 18 | constructor(channelName: string) { 19 | this.channelName = channelName 20 | this.buses = new WeakMap() 21 | this.inboxQ = new Queue('MessageCenter:inboxQ') 22 | } 23 | 24 | listenTo(peer: NetworkPeer): void { 25 | this.getBus(peer) 26 | } 27 | 28 | sendToPeers(peers: Iterable, msg: Msg): void { 29 | for (const peer of peers) { 30 | this.sendToPeer(peer, msg) 31 | } 32 | } 33 | 34 | sendToPeer(peer: NetworkPeer, msg: Msg): void { 35 | const bus = this.getBus(peer) 36 | bus?.send(msg) 37 | } 38 | 39 | getBus(peer: NetworkPeer): MessageBus | undefined { 40 | if (!peer.connection) return 41 | 42 | return getOrCreate(this.buses, peer.connection, (conn) => { 43 | const bus = conn.openBus(this.channelName) 44 | 45 | bus.receiveQ.subscribe((msg) => { 46 | this.inboxQ.push({ 47 | sender: peer, 48 | channelName: this.channelName, 49 | msg, 50 | }) 51 | }) 52 | return bus 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/simple/src/simple.ts: -------------------------------------------------------------------------------- 1 | import { Repo } from "hypermerge" 2 | 3 | interface MyDoc { 4 | numbers: number[], 5 | foo?: string, 6 | bar?: string 7 | } 8 | 9 | const storage = require("random-access-memory") 10 | 11 | const repoA = new Repo({ storage }) 12 | const repoB = new Repo({ storage }) 13 | 14 | // DAT's discovery swarm or truly serverless discovery 15 | const DiscoverySwarm = require("discovery-swarm"); 16 | const defaults = require('dat-swarm-defaults') 17 | const discoveryA = new DiscoverySwarm(defaults({stream: repoA.stream, id: repoA.id })); 18 | const discoveryB= new DiscoverySwarm(defaults({stream: repoB.stream, id: repoB.id })); 19 | 20 | repoA.replicate(discoveryA) 21 | repoB.replicate(discoveryB) 22 | 23 | const docUrl = repoA.create({ numbers: [ 2,3,4 ]}) 24 | 25 | // this will block until the state has replicated to machine B 26 | 27 | repoA.watch(docUrl, state => { 28 | console.log("RepoA", state) 29 | // { numbers: [2,3,4] } 30 | // { numbers: [2,3,4,5], foo: "bar" } 31 | // { numbers: [2,3,4,5], foo: "bar" } // (local changes repeat) 32 | // { numbers: [1,2,3,4,5], foo: "bar", bar: "foo" } 33 | }) 34 | 35 | repoB.watch(docUrl, state => { 36 | console.log("RepoB", state) 37 | // { numbers: [1,2,3,4,5], foo: "bar", bar: "foo" } 38 | if (state.numbers.length == 5) { 39 | process.exit() 40 | } 41 | }) 42 | 43 | repoA.change(docUrl, state => { 44 | state.numbers.push(5) 45 | state.foo = "bar" 46 | }) 47 | 48 | repoB.change(docUrl, state => { 49 | state.numbers.unshift(1) 50 | state.bar = "foo" 51 | }) 52 | 53 | -------------------------------------------------------------------------------- /dist/MapSet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class MapSet { 4 | constructor() { 5 | this.map = new Map(); 6 | } 7 | add(key, val) { 8 | return this.merge(key, [val]); 9 | } 10 | set(key, val) { 11 | this.map.set(key, val); 12 | } 13 | values() { 14 | return [...this.map.values()]; 15 | } 16 | union() { 17 | const acc = []; 18 | for (const set of this.map.values()) { 19 | acc.push(...set); 20 | } 21 | return new Set(acc); 22 | } 23 | keys() { 24 | return [...this.map.keys()]; 25 | } 26 | merge(key, vals) { 27 | const current = this.get(key); 28 | const change = vals.some((val) => !current.has(val)); 29 | if (change) { 30 | this.map.set(key, new Set([...current, ...vals])); 31 | } 32 | return change; 33 | } 34 | delete(key) { 35 | const old = this.get(key); 36 | this.map.delete(key); 37 | return old; 38 | } 39 | remove(key, val) { 40 | this.get(key).delete(val); 41 | } 42 | keysWith(val) { 43 | const keys = new Set(); 44 | this.map.forEach((vals, key) => { 45 | if (vals.has(val)) { 46 | keys.add(key); 47 | } 48 | }); 49 | return keys; 50 | } 51 | get(key) { 52 | return this.map.get(key) || new Set(); 53 | } 54 | has(key, val) { 55 | return this.get(key).has(val); 56 | } 57 | } 58 | exports.default = MapSet; 59 | //# sourceMappingURL=MapSet.js.map -------------------------------------------------------------------------------- /dist/DocBackend.d.ts: -------------------------------------------------------------------------------- 1 | import { Change, BackendState as BackDoc, Patch } from 'automerge'; 2 | import Queue from './Queue'; 3 | import { Clock } from './Clock'; 4 | import { ActorId, DocId } from './Misc'; 5 | export declare type DocBackendMessage = ReadyMsg | ActorIdMsg | RemotePatchMsg | LocalPatchMsg; 6 | interface ReadyMsg { 7 | type: 'ReadyMsg'; 8 | doc: DocBackend; 9 | history?: number; 10 | patch?: Patch; 11 | } 12 | interface ActorIdMsg { 13 | type: 'ActorIdMsg'; 14 | id: DocId; 15 | actorId: ActorId; 16 | } 17 | interface RemotePatchMsg { 18 | type: 'RemotePatchMsg'; 19 | doc: DocBackend; 20 | patch: Patch; 21 | change?: Change; 22 | history: number; 23 | } 24 | interface LocalPatchMsg { 25 | type: 'LocalPatchMsg'; 26 | doc: DocBackend; 27 | patch: Patch; 28 | change: Change; 29 | history: number; 30 | } 31 | export declare class DocBackend { 32 | id: DocId; 33 | actorId?: ActorId; 34 | clock: Clock; 35 | back?: BackDoc; 36 | changes: Map; 37 | ready: Queue; 38 | updateQ: Queue; 39 | private localChangeQ; 40 | private remoteChangesQ; 41 | constructor(documentId: DocId, back?: BackDoc); 42 | applyRemoteChanges: (changes: Change[]) => void; 43 | applyLocalChange: (change: Change) => void; 44 | initActor: (actorId: ActorId) => void; 45 | updateClock(changes: Change[]): void; 46 | init: (changes: Change[], actorId?: ActorId | undefined) => void; 47 | subscribeToRemoteChanges(): void; 48 | subscribeToLocalChanges(): void; 49 | private bench; 50 | } 51 | export {}; 52 | -------------------------------------------------------------------------------- /dist/Queue.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Queue.js","sourceRoot":"","sources":["../src/Queue.ts"],"names":[],"mappings":";;;;;AAAA,oDAA0C;AAE1C,MAAqB,KAAK;IAOxB,YAAY,OAAe,SAAS;QAJ5B,UAAK,GAAQ,EAAE,CAAA;QA+Df,YAAO,GAAG,CAAC,IAAO,EAAE,EAAE;YAC5B,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;YACxB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,CAAC,CAAA;QA7DC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,GAAG,GAAG,eAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAA;IAC1B,CAAC;IAED,KAAK;QACH,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,CAAC,EAAqB;QACzB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;YAC/B,IAAI,IAAI,KAAK,SAAS;gBAAE,EAAE,CAAC,IAAI,CAAC,CAAA;SACjC;IACH,CAAC;IAED,IAAI,CAAC,UAA6B;QAChC,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE;YACnC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;gBACtB,IAAI,CAAC,WAAW,EAAE,CAAA;gBAClB,UAAU,CAAC,IAAI,CAAC,CAAA;YAClB,CAAC,CAAC,CAAA;SACH;IACH,CAAC;IAED,SAAS,CAAC,UAA6B;QACrC,IAAI,IAAI,CAAC,YAAY,EAAE;YACrB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,4CAA4C,CAAC,CAAA;SAC1E;QAED,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QAErB,IAAI,CAAC,YAAY,GAAG,UAAU,CAAA;QAE9B,+EAA+E;QAE/E,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU,EAAE;YACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;YAC/B,IAAI,IAAI,KAAK,SAAS,EAAE;gBACtB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAA;gBACtB,MAAK;aACN;YACD,UAAU,CAAC,IAAI,CAAC,CAAA;SACjB;IACH,CAAC;IAED,WAAW;QACT,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;QACvB,IAAI,CAAC,YAAY,GAAG,SAAS,CAAA;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAA;IAC1B,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IAC1B,CAAC;CAMF;AAtED,wBAsEC"} -------------------------------------------------------------------------------- /dist/hypercore.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"hypercore.js","sourceRoot":"","sources":["../src/hypercore.ts"],"names":[],"mappings":";;;;;;AAEA,oDAA2B;AAC3B,iCAAoC;AACpC,MAAM,GAAG,GAAG,eAAK,CAAC,WAAW,CAAC,CAAA;AAE9B,SAAS,SAAS,CAChB,EAAsB,EACtB,IAAa,EACb,KAAa,EACb,EAAuB;IAEvB,GAAG,CAAC,gBAAgB,SAAE,CAAC,EAAE,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAA;IAE3C,IAAI,KAAK,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACzC,IAAI,GAAG;gBAAE,GAAG,CAAC,uBAAuB,SAAE,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YAClD,IAAI,GAAG;gBAAE,MAAM,GAAG,CAAA;YAClB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACZ,CAAC,CAAC,CAAA;KACH;SAAM;QACL,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YACrD,IAAI,GAAG;gBAAE,GAAG,CAAC,0BAA0B,SAAE,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;YACrD,IAAI,GAAG;gBAAE,MAAM,GAAG,CAAA;YAClB,EAAE,CAAC,IAAI,CAAC,CAAA;QACV,CAAC,CAAC,CAAA;KACH;AACH,CAAC;AAED,SAAgB,QAAQ,CAAI,EAAsB,EAAE,IAAa,EAAE,EAAuB;IACxF,iDAAiD;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAA;IAEhC,GAAG,CAAC,YAAY,SAAE,CAAC,EAAE,CAAC,eAAe,MAAM,gBAAgB,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;IAEzE,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAA;IAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;IAE/D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAChB,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC9B,GAAG,CAAC,8BAA8B,SAAE,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBACtD,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAA;YAChC,CAAC,CAAC,CAAA;YACF,MAAK;SACN;KACF;AACH,CAAC;AAlBD,4BAkBC"} -------------------------------------------------------------------------------- /tests/TraverseLogic.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import * as TraverseLogic from '../src/TraverseLogic' 3 | import * as Automerge from 'automerge' 4 | 5 | test('Test TraverseLogic', (t) => { 6 | t.test('Test arrays', (t) => { 7 | t.plan(1) 8 | const val = [1, 2, 3, 4, 3, 2, 1] 9 | const select = (val: unknown) => val === 2 10 | const results = TraverseLogic.iterativeDfs(select, val) 11 | t.deepEqual(results, [2, 2]) 12 | }) 13 | 14 | t.test('Test objects', (t) => { 15 | t.plan(1) 16 | const val = { foo: { bar: 'baz' } } 17 | const select = (val: unknown) => val === 'baz' 18 | const results = TraverseLogic.iterativeDfs(select, val) 19 | t.deepEqual(results, ['baz']) 20 | }) 21 | 22 | t.test('Test does not iterate strings', (t) => { 23 | t.plan(1) 24 | const val = 'helloooooo' 25 | const select = (val: unknown) => val === 'o' 26 | const results = TraverseLogic.iterativeDfs(select, val) 27 | t.deepEqual(results, []) 28 | }) 29 | 30 | t.test('Test selects keys', (t) => { 31 | t.plan(1) 32 | const val = { foo: [{ bar: 'baz' }] } 33 | const select = (val: unknown) => val === 'bar' 34 | const results = TraverseLogic.iterativeDfs(select, val) 35 | t.deepEqual(results, ['bar']) 36 | }) 37 | 38 | t.test("Test Automerge.Text isn't traversed", function(t) { 39 | t.plan(1) 40 | const doc = Automerge.change(Automerge.init(), function(doc: any) { 41 | doc.text = new Automerge.Text() 42 | doc.text.insertAt(0, 't', 'e', 's', 't') 43 | }) 44 | const results = TraverseLogic.iterativeDfs((val: any) => val === 't', doc) 45 | t.equals(results.length, 0) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /dist/Crawler.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Crawler.js","sourceRoot":"","sources":["../src/Crawler.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oDAA2B;AAE3B,iCAA0E;AAE1E,2CAA4C;AAC5C,+DAAgD;AAGhD,MAAM,GAAG,GAAG,eAAK,CAAC,SAAS,CAAC,CAAA;AAE5B,MAAa,OAAO;IAKlB,YAAY,IAAkB;QAH9B,SAAI,GAAgB,IAAI,GAAG,EAAE,CAAA;QAC7B,YAAO,GAA6B,IAAI,GAAG,EAAE,CAAA;QAW7C,UAAK,GAAG,CAAC,MAAe,EAAE,EAAE;YAC1B,MAAM,GAAG,GAAG,mBAAY,CAAC,MAAM,CAAC,CAAA;YAChC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAM;YAC9B,GAAG,CAAC,YAAY,GAAG,EAAE,CAAC,CAAA;YAEtB,IAAI,eAAQ,CAAC,GAAG,CAAC,EAAE;gBACjB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;gBACzC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;gBAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;gBAC7B,YAAY,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;aAC5D;iBAAM,IAAI,0BAAc,CAAC,GAAG,CAAC,EAAE;gBAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;gBAClB,YAAY,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;aAChD;QACH,CAAC,CAAA;QAED,qBAAgB,GAAG,CAAC,GAAa,EAAE,EAAE;YACnC,MAAM,IAAI,GAAG,aAAa,CAAC,YAAY,CAAU,eAAe,EAAE,GAAG,CAAC,CAAA;YACtE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC1B,CAAC,CAAA;QA3BC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;IAED,KAAK,CAAC,GAAW;QACf,GAAG,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACjB,CAAC;IAuBD,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAA;QAChD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QACpB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;CACF;AAxCD,0BAwCC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,CAAC,eAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAChC,OAAO,eAAQ,CAAC,GAAG,CAAC,IAAI,0BAAc,CAAC,GAAG,CAAC,CAAA;AAC7C,CAAC"} -------------------------------------------------------------------------------- /src/Crawler.ts: -------------------------------------------------------------------------------- 1 | import Debug from './Debug' 2 | import { RepoFrontend } from './RepoFrontend' 3 | import { DocUrl, withoutQuery, isString, isDocUrl, BaseUrl } from './Misc' 4 | import { Handle } from './Handle' 5 | import { isHyperfileUrl } from './FileStore' 6 | import * as TraverseLogic from './TraverseLogic' 7 | import { Doc } from 'automerge' 8 | 9 | const log = Debug('Crawler') 10 | 11 | export class Crawler { 12 | repo: RepoFrontend 13 | seen: Set = new Set() 14 | handles: Map> = new Map() 15 | 16 | constructor(repo: RepoFrontend) { 17 | this.repo = repo 18 | } 19 | 20 | crawl(url: DocUrl) { 21 | log(`Crawling from root ${url}`) 22 | this.onUrl(url) 23 | } 24 | 25 | onUrl = (urlVal: BaseUrl) => { 26 | const url = withoutQuery(urlVal) 27 | if (this.seen.has(url)) return 28 | log(`Crawling ${url}`) 29 | 30 | if (isDocUrl(url)) { 31 | const handle = this.repo.open(url, false) 32 | this.seen.add(url) 33 | this.handles.set(url, handle) 34 | setImmediate(() => handle.subscribe(this.onDocumentUpdate)) 35 | } else if (isHyperfileUrl(url)) { 36 | this.seen.add(url) 37 | setImmediate(() => this.repo.files.header(url)) 38 | } 39 | } 40 | 41 | onDocumentUpdate = (doc: Doc) => { 42 | const urls = TraverseLogic.iterativeDfs(isHypermergeUrl, doc) 43 | urls.forEach(this.onUrl) 44 | } 45 | 46 | close() { 47 | this.handles.forEach((handle) => handle.close()) 48 | this.handles.clear() 49 | this.seen.clear() 50 | } 51 | } 52 | 53 | function isHypermergeUrl(val: unknown): boolean { 54 | if (!isString(val)) return false 55 | return isDocUrl(val) || isHyperfileUrl(val) 56 | } 57 | -------------------------------------------------------------------------------- /dist/Debug.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.assignGlobal = exports.trace = exports.log = void 0; 26 | const Keys = __importStar(require("./Keys")); 27 | const debug_1 = __importDefault(require("debug")); 28 | debug_1.default.formatters.b = Keys.encode; 29 | exports.log = debug_1.default('hypermerge'); 30 | exports.default = (namespace) => exports.log.extend(namespace); 31 | exports.trace = (label) => (x, ...args) => { 32 | console.log(`${label}:`, x, ...args); 33 | return x; 34 | }; 35 | function assignGlobal(objs) { 36 | Object.assign(global, objs); 37 | } 38 | exports.assignGlobal = assignGlobal; 39 | //# sourceMappingURL=Debug.js.map -------------------------------------------------------------------------------- /dist/Repo.d.ts: -------------------------------------------------------------------------------- 1 | import { Options, RepoBackend } from './RepoBackend'; 2 | import { RepoFrontend } from './RepoFrontend'; 3 | import { Handle } from './Handle'; 4 | import { PublicMetadata } from './Metadata'; 5 | import { Clock } from './Clock'; 6 | import { DocUrl, HyperfileUrl, RepoId } from './Misc'; 7 | import FileServerClient from './FileServerClient'; 8 | import { Swarm, JoinOptions } from './SwarmInterface'; 9 | import { Doc, Proxy } from 'automerge'; 10 | import { CryptoClient } from './CryptoClient'; 11 | export declare class Repo { 12 | front: RepoFrontend; 13 | back: RepoBackend; 14 | id: RepoId; 15 | create: (init?: T) => DocUrl; 16 | open: (id: DocUrl) => Handle; 17 | destroy: (id: DocUrl) => void; 18 | /** @deprecated Use addSwarm */ 19 | setSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void; 20 | addSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void; 21 | removeSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void; 22 | message: (url: DocUrl, message: any) => void; 23 | crypto: CryptoClient; 24 | files: FileServerClient; 25 | startFileServer: (fileServerPath: string) => void; 26 | fork: (url: DocUrl) => DocUrl; 27 | watch: (url: DocUrl, cb: (val: Doc, clock?: Clock, index?: number) => void) => Handle; 28 | doc: (url: DocUrl, cb?: (val: Doc, clock?: Clock) => void) => Promise>; 29 | merge: (url: DocUrl, target: DocUrl) => void; 30 | change: (url: DocUrl, fn: (state: Proxy) => void) => void; 31 | materialize: (url: DocUrl, seq: number, cb: (val: Doc) => void) => void; 32 | meta: (url: DocUrl | HyperfileUrl, cb: (meta: PublicMetadata | undefined) => void) => void; 33 | close: () => void; 34 | constructor(opts: Options); 35 | } 36 | -------------------------------------------------------------------------------- /tests/Heartbeat.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import Heartbeat, { Interval } from '../src/Heartbeat' 3 | 4 | test('Interval', (t) => { 5 | t.test('recurs', (t) => { 6 | t.plan(3) 7 | var i = 0 8 | var failure = setTimeout(() => { 9 | t.fail('timed out without cancelation') 10 | }, 150) 11 | 12 | const interval = new Interval(10, () => { 13 | i += 1 14 | t.pass(`interval occurred ${i} times`) 15 | if (i >= 3) { 16 | interval.stop() 17 | clearTimeout(failure) 18 | t.end() 19 | } 20 | }) 21 | interval.start() 22 | }) 23 | }) 24 | 25 | test('Heartbeat', (t) => { 26 | t.test('beats', (t) => { 27 | t.plan(1) 28 | const heartbeat = new Heartbeat(10, { 29 | onBeat: () => { 30 | t.pass('heartbeat occurred') 31 | heartbeat.stop() 32 | t.end() 33 | }, 34 | onTimeout: () => { 35 | t.fail('timed out') 36 | }, 37 | }) 38 | heartbeat.start() 39 | }) 40 | 41 | t.test('times out', (t) => { 42 | t.plan(1) 43 | const heartbeat = new Heartbeat(10, { 44 | onBeat: () => {}, 45 | onTimeout: () => { 46 | t.pass('heartbeat occurred') 47 | heartbeat.stop() 48 | t.end() 49 | }, 50 | }) 51 | heartbeat.start() 52 | }) 53 | 54 | t.test('bumps', (t) => { 55 | t.plan(1) 56 | 57 | setTimeout(() => { 58 | t.pass("didn't time out after 500ms despite a standard timeout of ~100ms") 59 | heartbeat.stop() 60 | t.end() 61 | }, 250) 62 | const heartbeat = new Heartbeat(10, { 63 | onBeat: () => { 64 | heartbeat.bump() 65 | }, 66 | onTimeout: () => { 67 | t.fail('timeout occurred') 68 | }, 69 | }) 70 | heartbeat.start() 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /src/Heartbeat.ts: -------------------------------------------------------------------------------- 1 | export interface Handlers { 2 | onBeat: () => void 3 | onTimeout: () => void 4 | } 5 | 6 | export default class Heartbeat { 7 | interval: Interval 8 | timeout: Timeout 9 | beating: boolean 10 | 11 | constructor(public ms: number, { onBeat, onTimeout }: Handlers) { 12 | this.beating = false 13 | this.interval = new Interval(ms, onBeat) 14 | this.timeout = new Timeout(ms * 10, () => { 15 | this.stop() 16 | onTimeout() 17 | }) 18 | } 19 | 20 | start(): this { 21 | if (this.beating) return this 22 | 23 | this.interval.start() 24 | this.timeout.start() 25 | this.beating = true 26 | 27 | return this 28 | } 29 | 30 | stop(): this { 31 | if (!this.beating) return this 32 | 33 | this.interval.stop() 34 | this.timeout.stop() 35 | this.beating = false 36 | 37 | return this 38 | } 39 | 40 | bump() { 41 | if (!this.beating) return 42 | 43 | this.timeout.bump() 44 | } 45 | } 46 | 47 | export class Interval { 48 | constructor(public ms: number, public onInterval: () => void) {} 49 | 50 | start() { 51 | this.stop() 52 | const id = setInterval(() => { 53 | this.onInterval() 54 | }, this.ms) 55 | 56 | this.stop = () => { 57 | clearInterval(id) 58 | delete this.stop 59 | } 60 | } 61 | 62 | stop() {} 63 | } 64 | 65 | export class Timeout { 66 | constructor(public ms: number, public onTimeout: () => void) {} 67 | 68 | start() { 69 | this.bump() 70 | } 71 | 72 | stop() {} 73 | 74 | bump() { 75 | this.stop() 76 | 77 | const id = setTimeout(() => { 78 | delete this.stop 79 | this.stop() 80 | this.onTimeout() 81 | }, this.ms) 82 | 83 | this.stop = () => { 84 | delete this.stop 85 | clearTimeout(id) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dist/MapSet.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"MapSet.js","sourceRoot":"","sources":["../src/MapSet.ts"],"names":[],"mappings":";;AAAA,MAAqB,MAAM;IAA3B;QACU,QAAG,GAAmB,IAAI,GAAG,EAAE,CAAA;IA8DzC,CAAC;IA5DC,GAAG,CAAC,GAAM,EAAE,GAAM;QAChB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAC/B,CAAC;IAED,GAAG,CAAC,GAAM,EAAE,GAAW;QACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACxB,CAAC;IAED,MAAM;QACJ,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAA;IAC/B,CAAC;IAED,KAAK;QACH,MAAM,GAAG,GAAQ,EAAE,CAAA;QACnB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE;YACnC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAA;SACjB;QACD,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7B,CAAC;IAED,KAAK,CAAC,GAAM,EAAE,IAAS;QACrB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACpD,IAAI,MAAM,EAAE;YACV,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;SAClD;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAED,MAAM,CAAC,GAAM;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACpB,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,MAAM,CAAC,GAAM,EAAE,GAAM;QACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IAED,QAAQ,CAAC,GAAM;QACb,MAAM,IAAI,GAAG,IAAI,GAAG,EAAK,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAY,EAAE,GAAM,EAAE,EAAE;YACxC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;gBACjB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;aACd;QACH,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,GAAG,CAAC,GAAM;QACR,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,CAAA;IACvC,CAAC;IAED,GAAG,CAAC,GAAM,EAAE,GAAM;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC/B,CAAC;CACF;AA/DD,yBA+DC"} -------------------------------------------------------------------------------- /dist/hypercore.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.readFeed = void 0; 7 | const Debug_1 = __importDefault(require("./Debug")); 8 | const Misc_1 = require("./Misc"); 9 | const log = Debug_1.default('hypercore'); 10 | function readFeedN(id, feed, index, cb) { 11 | log(`readFeedN id=${Misc_1.ID(id)} (0..${index})`); 12 | if (index === 0) { 13 | feed.get(0, { wait: false }, (err, data) => { 14 | if (err) 15 | log(`feed.get() error id=${Misc_1.ID(id)}`, err); 16 | if (err) 17 | throw err; 18 | cb([data]); 19 | }); 20 | } 21 | else { 22 | feed.getBatch(0, index, { wait: false }, (err, data) => { 23 | if (err) 24 | log(`feed.getBatch error id=${Misc_1.ID(id)}`, err); 25 | if (err) 26 | throw err; 27 | cb(data); 28 | }); 29 | } 30 | } 31 | function readFeed(id, feed, cb) { 32 | // const id = feed.id.toString('hex').slice(0,4) 33 | const length = feed.downloaded(); 34 | log(`readFeed ${Misc_1.ID(id)} downloaded=${length} feed.length=${feed.length}`); 35 | if (length === 0) 36 | return cb([]); 37 | if (feed.has(0, length)) 38 | return readFeedN(id, feed, length, cb); 39 | for (let i = 0; i < length; i++) { 40 | if (!feed.has(i)) { 41 | feed.clear(i, feed.length, () => { 42 | log(`post clear -- readFeedN id=${Misc_1.ID(id)} n=${i - 1}`); 43 | readFeedN(id, feed, i - 1, cb); 44 | }); 45 | break; 46 | } 47 | } 48 | } 49 | exports.readFeed = readFeed; 50 | //# sourceMappingURL=hypercore.js.map -------------------------------------------------------------------------------- /src/Queue.ts: -------------------------------------------------------------------------------- 1 | import Debug, { IDebugger } from './Debug' 2 | 3 | export default class Queue { 4 | push: (item: T) => void 5 | name: string 6 | private queue: T[] = [] 7 | private log: IDebugger 8 | private subscription?: (item: T) => void 9 | 10 | constructor(name: string = 'unknown') { 11 | this.name = name 12 | this.log = Debug(`Queue:${name}`) 13 | this.push = this.enqueue 14 | } 15 | 16 | first(): Promise { 17 | return new Promise((res) => { 18 | this.once(res) 19 | }) 20 | } 21 | 22 | drain(fn: (item: T) => void): void { 23 | while (this.queue.length) { 24 | const item = this.queue.shift() 25 | if (item !== undefined) fn(item) 26 | } 27 | } 28 | 29 | once(subscriber: (item: T) => void) { 30 | if (this.subscription === undefined) { 31 | this.subscribe((item) => { 32 | this.unsubscribe() 33 | subscriber(item) 34 | }) 35 | } 36 | } 37 | 38 | subscribe(subscriber: (item: T) => void) { 39 | if (this.subscription) { 40 | throw new Error(`${this.name}: only one subscriber at a time to a queue`) 41 | } 42 | 43 | this.log('subscribe') 44 | 45 | this.subscription = subscriber 46 | 47 | // this is so push(), unsubscribe(), re-subscribe() will processing the backlog 48 | 49 | while (this.subscription === subscriber) { 50 | const item = this.queue.shift() 51 | if (item === undefined) { 52 | this.push = subscriber 53 | break 54 | } 55 | subscriber(item) 56 | } 57 | } 58 | 59 | unsubscribe() { 60 | this.log('unsubscribe') 61 | this.subscription = undefined 62 | this.push = this.enqueue 63 | } 64 | 65 | get length() { 66 | return this.queue.length 67 | } 68 | 69 | private enqueue = (item: T) => { 70 | this.log('queued', item) 71 | this.queue.push(item) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /dist/Heartbeat.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Heartbeat.js","sourceRoot":"","sources":["../src/Heartbeat.ts"],"names":[],"mappings":";;;AAKA,MAAqB,SAAS;IAK5B,YAAmB,EAAU,EAAE,EAAE,MAAM,EAAE,SAAS,EAAY;QAA3C,OAAE,GAAF,EAAE,CAAQ;QAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;QACxC,IAAI,CAAC,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE;YACvC,IAAI,CAAC,IAAI,EAAE,CAAA;YACX,SAAS,EAAE,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAE7B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QACpB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;QAEnB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAE9B,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACpB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QAEpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;IACrB,CAAC;CACF;AAvCD,4BAuCC;AAED,MAAa,QAAQ;IACnB,YAAmB,EAAU,EAAS,UAAsB;QAAzC,OAAE,GAAF,EAAE,CAAQ;QAAS,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEhE,KAAK;QACH,IAAI,CAAC,IAAI,EAAE,CAAA;QACX,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC,UAAU,EAAE,CAAA;QACnB,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;QAEX,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE;YACf,aAAa,CAAC,EAAE,CAAC,CAAA;YACjB,OAAO,IAAI,CAAC,IAAI,CAAA;QAClB,CAAC,CAAA;IACH,CAAC;IAED,IAAI,KAAI,CAAC;CACV;AAhBD,4BAgBC;AAED,MAAa,OAAO;IAClB,YAAmB,EAAU,EAAS,SAAqB;QAAxC,OAAE,GAAF,EAAE,CAAQ;QAAS,cAAS,GAAT,SAAS,CAAY;IAAG,CAAC;IAE/D,KAAK;QACH,IAAI,CAAC,IAAI,EAAE,CAAA;IACb,CAAC;IAED,IAAI,KAAI,CAAC;IAET,IAAI;QACF,IAAI,CAAC,IAAI,EAAE,CAAA;QAEX,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,EAAE;YACzB,OAAO,IAAI,CAAC,IAAI,CAAA;YAChB,IAAI,CAAC,IAAI,EAAE,CAAA;YACX,IAAI,CAAC,SAAS,EAAE,CAAA;QAClB,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;QAEX,IAAI,CAAC,IAAI,GAAG,GAAG,EAAE;YACf,OAAO,IAAI,CAAC,IAAI,CAAA;YAChB,YAAY,CAAC,EAAE,CAAC,CAAA;QAClB,CAAC,CAAA;IACH,CAAC;CACF;AAvBD,0BAuBC"} -------------------------------------------------------------------------------- /tests/PeerConnection.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { expect, testConnectionPair } from './misc' 3 | import { assignGlobal } from '../src/Debug' 4 | 5 | test('PeerConnection', (t) => { 6 | const [connA, connB] = testConnectionPair() 7 | 8 | assignGlobal({ connA, connB }) 9 | 10 | t.test('channels', (t) => { 11 | t.plan(0) 12 | 13 | const channel = connA.openChannel('ConnTest') 14 | channel.write('from_connA_1') 15 | channel.write('from_connA_2') 16 | 17 | connB 18 | .openChannel('ConnTest') 19 | .on( 20 | 'data', 21 | expect(t, String, [ 22 | ['from_connA_1', 'connB gets first connA msg'], 23 | ['from_connA_2', 'connB gets second connA message'], 24 | ]) 25 | ) 26 | }) 27 | 28 | t.test('delayed channels', (t) => { 29 | t.plan(0) 30 | 31 | const channelA = connA.openChannel('DelayedConnTest') 32 | assignGlobal({ channelA }) 33 | channelA.on( 34 | 'data', 35 | expect(t, String, [ 36 | ['delayed_from_connB_1', 'connA gets delayed message from connB'], 37 | ['delayed_from_connB_2', 'connA gets another delayed message from connB'], 38 | ]) 39 | ) 40 | 41 | channelA.write('delayed_from_connA_1') 42 | channelA.write('delayed_from_connA_2') 43 | 44 | setTimeout(() => { 45 | const channelB = connB.openChannel('DelayedConnTest') 46 | assignGlobal({ channelB }) 47 | 48 | channelB.on( 49 | 'data', 50 | expect(t, String, [ 51 | ['delayed_from_connA_1', 'connB gets delayed message from connA'], 52 | ['delayed_from_connA_2', 'connB gets another delayed message from connA'], 53 | ]) 54 | ) 55 | 56 | channelB.write('delayed_from_connB_1') 57 | channelB.write('delayed_from_connB_2') 58 | }, 500) 59 | }) 60 | 61 | test.onFinish(() => { 62 | connA.close() 63 | connB.close() 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /examples/simple-repos/src/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Repo } from "hypermerge" 3 | import Hyperswarm from 'hyperswarm' 4 | 5 | const uuid = require('uuid/v4') 6 | const raf: Function = require("random-access-file") 7 | 8 | interface MyDoc { 9 | numbers: number[], 10 | foo?: string, 11 | bar?: string, 12 | status?: string 13 | } 14 | 15 | // Repos are created on disk at location "dbName" 16 | function myRepos(dbName: string) { 17 | return new Repo({path: dbName, memory: false}) 18 | } 19 | 20 | // Repos will connect through the swarm 21 | function getSwarm() { 22 | return Hyperswarm({ 23 | queue: { 24 | multiplex: true, 25 | }, 26 | } 27 | ) 28 | } 29 | 30 | const repoA = myRepos("repo-a") 31 | const repoB = myRepos("repo-b") 32 | 33 | repoA.addSwarm(getSwarm(),{announce: true}) 34 | repoB.addSwarm(getSwarm(), {announce: true}) 35 | 36 | // Create single test document 37 | const docUrl = repoA.create({ numbers: [ 2,3,4 ], status: "Create"}) 38 | 39 | console.log(docUrl) 40 | 41 | //Repo callbacks to watch for changes on the shared document 42 | repoB.watch(docUrl, state => { 43 | console.log("RepoB: -> ", state) 44 | }) 45 | 46 | repoA.watch(docUrl, state => { 47 | console.log("RepoA: ->", state) 48 | }) 49 | 50 | // Make changes to the single test document from RepoA and RepoB 51 | repoA.change(docUrl, state => { 52 | state.numbers.push(5) 53 | state.foo = "bar" 54 | }) 55 | 56 | repoB.change(docUrl, state => { 57 | state.numbers.push(6) 58 | state.status = "Updated" 59 | }) 60 | 61 | const sm = sign() 62 | 63 | async function sign() { 64 | const message = 'test message' 65 | const signedMessage = await repoA.crypto.sign(docUrl, message) 66 | const success = await repoA.crypto.verify(docUrl, signedMessage) 67 | console.log(signedMessage, success ? "Message verified" : "Message NOT verified") 68 | } 69 | 70 | -------------------------------------------------------------------------------- /dist/FileStore.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"FileStore.js","sourceRoot":"","sources":["../src/FileStore.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGA,yCAA4C;AAC5C,6CAA8B;AAC9B,yDAA0C;AAC1C,oDAA2B;AAC3B,+CAAmE;AAEtD,QAAA,UAAU,GAAG,EAAE,GAAG,IAAI,CAAA;AAUnC,MAAqB,SAAS;IAI5B,YAAY,KAAgB;QAC1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAK,CAAC,oBAAoB,CAAC,CAAA;IACjD,CAAC;IAEK,MAAM,CAAC,GAAiB;;YAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;QAC9D,CAAC;KAAA;IAEK,IAAI,CAAC,GAAiB;;YAC1B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;YAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QACzC,CAAC;KAAA;IAEK,KAAK,CAAC,MAAgB,EAAE,QAAgB;;YAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAA;YAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YAE5C,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;YAE1D,OAAO,IAAI,OAAO,CAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;gBACtC,MAAM,WAAW,GAAG,IAAI,gCAAkB,CAAC,kBAAU,CAAC,CAAA;gBACtD,MAAM,UAAU,GAAG,IAAI,6BAAe,CAAC,QAAQ,CAAC,CAAA;gBAEhD,MAAM;qBACH,IAAI,CAAC,UAAU,CAAC;qBAChB,IAAI,CAAC,WAAW,CAAC;qBACjB,IAAI,CAAC,YAAY,CAAC;qBAClB,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;qBAC9B,EAAE,CAAC,QAAQ,EAAE,GAAS,EAAE;oBACvB,MAAM,MAAM,GAAW;wBACrB,GAAG,EAAE,cAAc,CAAC,MAAM,CAAC;wBAC3B,QAAQ;wBACR,IAAI,EAAE,WAAW,CAAC,cAAc;wBAChC,MAAM,EAAE,WAAW,CAAC,UAAU;wBAC9B,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;qBACtC,CAAA;oBAED,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAA;oBAC7D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;oBAC1B,GAAG,CAAC,MAAM,CAAC,CAAA;gBACb,CAAC,CAAA,CAAC,CAAA;YACN,CAAC,CAAC,CAAA;QACJ,CAAC;KAAA;CACF;AAhDD,4BAgDC;AAED,SAAgB,cAAc,CAAC,GAAW;IACxC,OAAO,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACtC,CAAC;AAFD,wCAEC;AAED,SAAS,cAAc,CAAC,MAAc;IACpC,OAAO,cAAc,MAAM,EAAkB,CAAA;AAC/C,CAAC;AAED,SAAS,QAAQ,CAAC,YAA0B;IAC1C,OAAQ,0BAAe,CAAC,YAAY,CAAsB,CAAA;AAC5D,CAAC"} -------------------------------------------------------------------------------- /dist/ClockStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { RepoId, DocId } from './Misc'; 3 | import * as Clock from './Clock'; 4 | import { Database } from './SqlDatabase'; 5 | import Queue from './Queue'; 6 | export interface ClockMap { 7 | [documentId: string]: Clock.Clock; 8 | } 9 | export declare type ClockDescriptor = [Clock.Clock, DocId, RepoId]; 10 | export default class ClockStore { 11 | db: Database; 12 | updateQ: Queue; 13 | private preparedGet; 14 | private preparedInsert; 15 | private preparedDelete; 16 | private preparedAllRepoIds; 17 | private preparedAllDocumentIds; 18 | private preparedAllForDocumentId; 19 | constructor(db: Database); 20 | /** 21 | * TODO: handle missing clocks better. Currently returns an empty clock (i.e. an empty object) 22 | */ 23 | get(repoId: RepoId, documentId: DocId): Clock.Clock; 24 | has(repoId: RepoId, documentId: DocId): boolean; 25 | /** 26 | * Retrieve the clocks for all given documents. If we don't have a clock 27 | * for a document, the resulting ClockMap won't have an entry for that document id. 28 | */ 29 | getMultiple(repoId: RepoId, documentIds: DocId[]): ClockMap; 30 | /** 31 | * Update an existing clock with a new clock, merging the two. 32 | * If no clock exists in the data store, the new clock is stored as-is. 33 | */ 34 | update(repoId: RepoId, documentId: DocId, clock: Clock.Clock): ClockDescriptor; 35 | /** 36 | * Hard set of a clock. Will clear any clock values that exist for the given document id 37 | * and set explicitly the passed in clock. 38 | */ 39 | set(repoId: RepoId, documentId: DocId, clock: Clock.Clock): ClockDescriptor; 40 | getAllDocumentIds(repoId: RepoId): DocId[]; 41 | getAllRepoIds(): RepoId[]; 42 | getAllForDocumentId(docId: DocId): ClockDescriptor[]; 43 | getMaximumSatisfiedClock(docId: DocId, candidate: Clock.Clock): Clock.Clock | undefined; 44 | } 45 | -------------------------------------------------------------------------------- /tests/Crypto.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import * as Crypto from '../src/Crypto' 3 | 4 | test('Test sign and verify', (t) => { 5 | t.plan(1) 6 | const keyPair = Crypto.encodedSigningKeyPair() 7 | const message = 'test message' 8 | const signature = Crypto.sign(keyPair.secretKey, Buffer.from(message)) 9 | const success = Crypto.verify(keyPair.publicKey, signature) 10 | t.true(success) 11 | }) 12 | 13 | test('Test verify fails with wrong signature', (t) => { 14 | t.plan(1) 15 | const keyPair = Crypto.encodedSigningKeyPair() 16 | const message = Buffer.from('test message') 17 | const message2 = Buffer.from('test message 2') 18 | const signedMessage = Crypto.sign(keyPair.secretKey, message2) 19 | const success = Crypto.verify(keyPair.publicKey, { 20 | message, 21 | signature: signedMessage.signature, 22 | }) 23 | t.false(success) 24 | }) 25 | 26 | // Note: this is mainly to document behavior. 27 | test('Test verify throws with garbage signature', (t) => { 28 | t.plan(1) 29 | const keyPair = Crypto.encodedSigningKeyPair() 30 | const message = Buffer.from('test message') 31 | const signature = 'contains non-base58 characters' as Crypto.EncodedSignature 32 | t.throws(() => Crypto.verify(keyPair.publicKey, { message, signature })) 33 | }) 34 | 35 | test('Test sealedBox and openSealedBox', (t) => { 36 | t.plan(1) 37 | const keyPair = Crypto.encodedEncryptionKeyPair() 38 | const message = 'test message' 39 | const sealedBox = Crypto.sealedBox(keyPair.publicKey, Buffer.from(message)) 40 | const openedMessage = Crypto.openSealedBox(keyPair, sealedBox) 41 | t.equal(openedMessage.toString(), message) 42 | }) 43 | 44 | test('Test openSealedBox throws with wrong key pair', (t) => { 45 | t.plan(1) 46 | const keyPair = Crypto.encodedEncryptionKeyPair() 47 | const keyPair2 = Crypto.encodedEncryptionKeyPair() 48 | const message = 'test message' 49 | const sealedBox = Crypto.sealedBox(keyPair.publicKey, Buffer.from(message)) 50 | t.throws(() => Crypto.openSealedBox(keyPair2, sealedBox)) 51 | }) 52 | -------------------------------------------------------------------------------- /dist/NetworkPeer.d.ts: -------------------------------------------------------------------------------- 1 | import { RepoId } from './Misc'; 2 | import PeerConnection from './PeerConnection'; 3 | import Queue from './Queue'; 4 | import * as Keys from './Keys'; 5 | import WeakCache from './WeakCache'; 6 | import MessageBus from './MessageBus'; 7 | export declare type PeerId = RepoId & { 8 | __peerId: true; 9 | }; 10 | export declare type Msg = ConfirmConnectionMsg; 11 | export interface ConfirmConnectionMsg { 12 | type: 'ConfirmConnection'; 13 | } 14 | export default class NetworkPeer { 15 | selfId: PeerId; 16 | id: PeerId; 17 | pendingConnections: Set; 18 | connectionQ: Queue; 19 | closedConnectionCount: number; 20 | busCache: WeakCache>; 21 | isClosing: boolean; 22 | connection?: PeerConnection; 23 | constructor(selfId: PeerId, id: PeerId); 24 | get isConnected(): boolean; 25 | /** 26 | * Determines if we are the authority on which connection to use when 27 | * duplicate connections are created. 28 | * 29 | * @remarks 30 | * We need to ensure that two peers don't close the other's incoming 31 | * connection. Comparing our ids ensures only one of the two peers decides 32 | * which connection to close. 33 | */ 34 | get weHaveAuthority(): boolean; 35 | /** 36 | * Attempts to add a connection to this peer. 37 | * If this connection is a duplicate of an existing connection, we close it. 38 | * If we aren't the authority, and we don't have a confirmed connection, we 39 | * hold onto it and wait for a ConfirmConnection message. 40 | */ 41 | addConnection(conn: PeerConnection): void; 42 | pickNewConnection(): void; 43 | confirmConnection(conn: PeerConnection): void; 44 | closeConnection(conn: PeerConnection): void; 45 | close(): void; 46 | private send; 47 | private onMsg; 48 | private onConnectionClosed; 49 | } 50 | export declare function encodePeerId(key: Keys.PublicKey): PeerId; 51 | export declare function decodePeerId(id: PeerId): Keys.PublicKey; 52 | -------------------------------------------------------------------------------- /src/Keys.ts: -------------------------------------------------------------------------------- 1 | import * as Base58 from 'bs58' 2 | import * as crypto from 'hypercore-crypto' 3 | import { Key, PublicKey, SecretKey, DiscoveryKey, discoveryKey } from 'hypercore-crypto' 4 | import * as Crypto from './Crypto' 5 | 6 | export { Key, PublicKey, SecretKey, DiscoveryKey, discoveryKey } 7 | 8 | export type KeyId = Crypto.EncodedPublicSigningKey 9 | export type PublicId = KeyId & { __publicId: true } 10 | export type SecretId = KeyId & { __secretId: true } 11 | export type DiscoveryId = KeyId & { __discoveryId: true } 12 | 13 | export interface KeyBuffer { 14 | publicKey: PublicKey 15 | secretKey?: SecretKey 16 | } 17 | 18 | export interface KeyPair { 19 | publicKey: PublicId 20 | secretKey?: SecretId 21 | } 22 | 23 | export function create(): Required { 24 | return encodePair(crypto.keyPair()) 25 | } 26 | 27 | export function createBuffer(): Required { 28 | return crypto.keyPair() 29 | } 30 | 31 | export function decodePair(keys: KeyPair): KeyBuffer { 32 | return { 33 | publicKey: decode(keys.publicKey), 34 | secretKey: keys.secretKey ? decode(keys.secretKey) : undefined, 35 | } 36 | } 37 | 38 | export function encodePair(keys: Required): Required 39 | export function encodePair(keys: KeyBuffer): KeyPair 40 | export function encodePair(keys: KeyBuffer): KeyPair { 41 | return { 42 | publicKey: encode(keys.publicKey), 43 | secretKey: keys.secretKey ? encode(keys.secretKey) : undefined, 44 | } 45 | } 46 | 47 | export function decode(key: DiscoveryId): DiscoveryKey 48 | export function decode(key: SecretId): SecretKey 49 | export function decode(key: PublicId): PublicKey 50 | export function decode(key: KeyId): Key 51 | export function decode(key: string): Buffer { 52 | return Base58.decode(key) 53 | } 54 | 55 | export function encode(key: DiscoveryKey): DiscoveryId 56 | export function encode(key: SecretKey): SecretId 57 | export function encode(key: PublicKey): PublicId 58 | export function encode(key: Key): KeyId 59 | export function encode(key: Buffer): string { 60 | return Base58.encode(key) 61 | } 62 | -------------------------------------------------------------------------------- /src/TraverseLogic.ts: -------------------------------------------------------------------------------- 1 | // This *must* be the automerge used by hypermerge, otherwise the instanceof 2 | // checks below will fail. 3 | import Automerge from 'automerge' 4 | import { isPlainObject } from './Misc' 5 | 6 | export const WARNING_STACK_SIZE = 2000 7 | export interface SelectFn { 8 | (obj: unknown): boolean 9 | } 10 | 11 | // NOTE: no cycle detection. This function is intended to be used for traversing 12 | // a single document and cycles within a document are impossible. 13 | // TODO: type this against Doc? 14 | export function iterativeDfs(select: SelectFn, root: unknown): T[] { 15 | const stack = [root] 16 | const results: T[] = [] 17 | while (stack.length) { 18 | // Yell if we're traversing real deep into a document. 19 | if (stack.length > WARNING_STACK_SIZE) { 20 | console.warn( 21 | 'Traverse.iterativeDFS large stack size warning.', 22 | `Stack size: ${stack.length}`, 23 | root 24 | ) 25 | return results 26 | } 27 | const obj = stack.pop() 28 | // Note: Manually check for Automerge.Text and don't inspect these. This will 29 | // blow up the stack size (which may not actually matter, but there's no point 30 | // in checking Automerge.Text anyway) 31 | // TODO: genericize this, maybe with a skip function, e.g. `if (skip(obj)) {` 32 | if (obj instanceof Automerge.Text) { 33 | // eslint-disable-next-line no-continue 34 | continue 35 | } else if (isPlainObject(obj)) { 36 | Object.entries(obj).forEach((entry: unknown) => stack.push(entry)) 37 | } else if (obj && hasForEach(obj)) { 38 | obj.forEach((val: unknown) => stack.push(val)) 39 | } else if (select(obj)) { 40 | results.push(obj as T) 41 | } 42 | } 43 | return results 44 | } 45 | 46 | // We use `.forEach` rather than `Symbol.Iterator` because strings are 47 | // iterables that iterate through each character. 48 | interface WithForEach { 49 | forEach: (cb: (val: T) => void) => void 50 | } 51 | function hasForEach(val: unknown): val is WithForEach { 52 | return !!(val as any).forEach 53 | } 54 | -------------------------------------------------------------------------------- /dist/Heartbeat.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Timeout = exports.Interval = void 0; 4 | class Heartbeat { 5 | constructor(ms, { onBeat, onTimeout }) { 6 | this.ms = ms; 7 | this.beating = false; 8 | this.interval = new Interval(ms, onBeat); 9 | this.timeout = new Timeout(ms * 10, () => { 10 | this.stop(); 11 | onTimeout(); 12 | }); 13 | } 14 | start() { 15 | if (this.beating) 16 | return this; 17 | this.interval.start(); 18 | this.timeout.start(); 19 | this.beating = true; 20 | return this; 21 | } 22 | stop() { 23 | if (!this.beating) 24 | return this; 25 | this.interval.stop(); 26 | this.timeout.stop(); 27 | this.beating = false; 28 | return this; 29 | } 30 | bump() { 31 | if (!this.beating) 32 | return; 33 | this.timeout.bump(); 34 | } 35 | } 36 | exports.default = Heartbeat; 37 | class Interval { 38 | constructor(ms, onInterval) { 39 | this.ms = ms; 40 | this.onInterval = onInterval; 41 | } 42 | start() { 43 | this.stop(); 44 | const id = setInterval(() => { 45 | this.onInterval(); 46 | }, this.ms); 47 | this.stop = () => { 48 | clearInterval(id); 49 | delete this.stop; 50 | }; 51 | } 52 | stop() { } 53 | } 54 | exports.Interval = Interval; 55 | class Timeout { 56 | constructor(ms, onTimeout) { 57 | this.ms = ms; 58 | this.onTimeout = onTimeout; 59 | } 60 | start() { 61 | this.bump(); 62 | } 63 | stop() { } 64 | bump() { 65 | this.stop(); 66 | const id = setTimeout(() => { 67 | delete this.stop; 68 | this.stop(); 69 | this.onTimeout(); 70 | }, this.ms); 71 | this.stop = () => { 72 | delete this.stop; 73 | clearTimeout(id); 74 | }; 75 | } 76 | } 77 | exports.Timeout = Timeout; 78 | //# sourceMappingURL=Heartbeat.js.map -------------------------------------------------------------------------------- /dist/Queue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Debug_1 = __importDefault(require("./Debug")); 7 | class Queue { 8 | constructor(name = 'unknown') { 9 | this.queue = []; 10 | this.enqueue = (item) => { 11 | this.log('queued', item); 12 | this.queue.push(item); 13 | }; 14 | this.name = name; 15 | this.log = Debug_1.default(`Queue:${name}`); 16 | this.push = this.enqueue; 17 | } 18 | first() { 19 | return new Promise((res) => { 20 | this.once(res); 21 | }); 22 | } 23 | drain(fn) { 24 | while (this.queue.length) { 25 | const item = this.queue.shift(); 26 | if (item !== undefined) 27 | fn(item); 28 | } 29 | } 30 | once(subscriber) { 31 | if (this.subscription === undefined) { 32 | this.subscribe((item) => { 33 | this.unsubscribe(); 34 | subscriber(item); 35 | }); 36 | } 37 | } 38 | subscribe(subscriber) { 39 | if (this.subscription) { 40 | throw new Error(`${this.name}: only one subscriber at a time to a queue`); 41 | } 42 | this.log('subscribe'); 43 | this.subscription = subscriber; 44 | // this is so push(), unsubscribe(), re-subscribe() will processing the backlog 45 | while (this.subscription === subscriber) { 46 | const item = this.queue.shift(); 47 | if (item === undefined) { 48 | this.push = subscriber; 49 | break; 50 | } 51 | subscriber(item); 52 | } 53 | } 54 | unsubscribe() { 55 | this.log('unsubscribe'); 56 | this.subscription = undefined; 57 | this.push = this.enqueue; 58 | } 59 | get length() { 60 | return this.queue.length; 61 | } 62 | } 63 | exports.default = Queue; 64 | //# sourceMappingURL=Queue.js.map -------------------------------------------------------------------------------- /examples/chat/src/ui.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Diffy from 'diffy' 4 | import DiffyInput from 'diffy/input' 5 | import stripAnsi from 'strip-ansi' 6 | import Channel from './channel' 7 | 8 | const diffy = Diffy({ fullscreen: true }) 9 | const input = DiffyInput({ showCursor: true }) 10 | 11 | function initUI(channel: Channel) { 12 | render(channel) 13 | 14 | input.on('enter', (line: string) => channel.addMessageToDoc(line)) 15 | input.on('update', () => render(channel)) 16 | channel.on('updated', () => render(channel)) 17 | 18 | // For network connection display 19 | setInterval(() => { 20 | render(channel) 21 | }, 3000) 22 | } 23 | 24 | function render(channel: Channel) { 25 | const userNick = channel.nick 26 | const url = channel.url 27 | const doc = channel.doc 28 | 29 | if (doc) { 30 | diffy.render(() => { 31 | let output = '' 32 | output += `Join: yarn run chat ${url}\n` 33 | output += `${channel.getNumConnections()} connections. ` 34 | output += `Use Ctrl-C to exit.\n\n` 35 | const displayMessages: string[] = [] 36 | const { messages } = doc 37 | Object.keys(messages) 38 | .sort() 39 | .forEach((key) => { 40 | if (key === '_objectId') return 41 | if (key === '_conflicts') return 42 | const { nick, content, joined } = messages[key] 43 | if (joined) { 44 | displayMessages.push(`${nick} has joined.`) 45 | } else { 46 | if (content) { 47 | displayMessages.push(`${nick}: ${content}`) 48 | } 49 | } 50 | }) 51 | // Delete old messages 52 | const maxMessages = diffy.height - output.split('\n').length - 2 53 | displayMessages.splice(0, displayMessages.length - maxMessages) 54 | displayMessages.forEach((line) => { 55 | output += stripAnsi(line).substr(0, diffy.width - 2) + '\n' 56 | }) 57 | for (let i = displayMessages.length; i < maxMessages; i++) { 58 | output += '\n' 59 | } 60 | output += `\n[${userNick}] ${input.line()}` 61 | return output 62 | }) 63 | } 64 | } 65 | 66 | export default initUI 67 | -------------------------------------------------------------------------------- /dist/TraverseLogic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.iterativeDfs = exports.WARNING_STACK_SIZE = void 0; 7 | // This *must* be the automerge used by hypermerge, otherwise the instanceof 8 | // checks below will fail. 9 | const automerge_1 = __importDefault(require("automerge")); 10 | const Misc_1 = require("./Misc"); 11 | exports.WARNING_STACK_SIZE = 2000; 12 | // NOTE: no cycle detection. This function is intended to be used for traversing 13 | // a single document and cycles within a document are impossible. 14 | // TODO: type this against Doc? 15 | function iterativeDfs(select, root) { 16 | const stack = [root]; 17 | const results = []; 18 | while (stack.length) { 19 | // Yell if we're traversing real deep into a document. 20 | if (stack.length > exports.WARNING_STACK_SIZE) { 21 | console.warn('Traverse.iterativeDFS large stack size warning.', `Stack size: ${stack.length}`, root); 22 | return results; 23 | } 24 | const obj = stack.pop(); 25 | // Note: Manually check for Automerge.Text and don't inspect these. This will 26 | // blow up the stack size (which may not actually matter, but there's no point 27 | // in checking Automerge.Text anyway) 28 | // TODO: genericize this, maybe with a skip function, e.g. `if (skip(obj)) {` 29 | if (obj instanceof automerge_1.default.Text) { 30 | // eslint-disable-next-line no-continue 31 | continue; 32 | } 33 | else if (Misc_1.isPlainObject(obj)) { 34 | Object.entries(obj).forEach((entry) => stack.push(entry)); 35 | } 36 | else if (obj && hasForEach(obj)) { 37 | obj.forEach((val) => stack.push(val)); 38 | } 39 | else if (select(obj)) { 40 | results.push(obj); 41 | } 42 | } 43 | return results; 44 | } 45 | exports.iterativeDfs = iterativeDfs; 46 | function hasForEach(val) { 47 | return !!val.forEach; 48 | } 49 | //# sourceMappingURL=TraverseLogic.js.map -------------------------------------------------------------------------------- /tests/ReplicationManager.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { testStorageFn, testPeerPair, testKeyPair, testDb } from './misc' 3 | import ReplicationManager from '../src/ReplicationManager' 4 | import FeedStore from '../src/FeedStore' 5 | 6 | test('ReplicationManager', (t) => { 7 | const [peerA, peerB] = testPeerPair() 8 | 9 | const feedsA = new FeedStore(testDb(), testStorageFn()) 10 | const feedsB = new FeedStore(testDb(), testStorageFn()) 11 | 12 | const replA = new ReplicationManager(feedsA) 13 | const replB = new ReplicationManager(feedsB) 14 | 15 | peerA.connectionQ.subscribe(() => replA.onPeer(peerA)) 16 | peerB.connectionQ.subscribe(() => replB.onPeer(peerB)) 17 | 18 | t.test('feed replication', async (t) => { 19 | t.plan(3) 20 | 21 | const feedId = await feedsA.create(testKeyPair()) 22 | 23 | feedsA.append(feedId, Buffer.from('testing')) 24 | 25 | feedsB.read(feedId, 0).then((data) => { 26 | t.equal(data.toString(), 'testing', 'feedsB gets block from feedsA') 27 | }) 28 | 29 | t.test('newly created feeds are replicated', async (t) => { 30 | t.plan(1) 31 | 32 | const feedId2 = await feedsB.create(testKeyPair()) 33 | 34 | feedsB.append(feedId2, Buffer.from('testing2')) 35 | feedsA.read(feedId2, 0).then((data2) => { 36 | t.equal(data2.toString(), 'testing2', 'feedsA gets late block from feedsB') 37 | }) 38 | }) 39 | 40 | t.test('empty feeds', async (t) => { 41 | t.plan(2) 42 | 43 | const feedId = await feedsA.create(testKeyPair()) 44 | const feed = await feedsB.getFeed(feedId) 45 | feed.head((err: Error | null) => { 46 | t.equal(err && err.message, 'feed is empty', 'gets feed is empty error') 47 | 48 | feedsB.head(feedId).then((block) => { 49 | t.deepEqual(block, Buffer.from('empty_feed_block_2'), 'gets head block') 50 | }) 51 | 52 | feedsA.append(feedId, Buffer.from('empty_feed_block_1')) 53 | feedsA.append(feedId, Buffer.from('empty_feed_block_2')) 54 | }) 55 | }) 56 | }) 57 | 58 | test.onFinish(async () => { 59 | replA.close() 60 | replB.close() 61 | 62 | feedsA.close() 63 | feedsB.close() 64 | 65 | peerA.close() 66 | peerB.close() 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /dist/Metadata.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import Queue from './Queue'; 3 | import { Clock } from './Clock'; 4 | import { DocUrl, DocId, ActorId, BaseUrl, HyperfileId, HyperfileUrl } from './Misc'; 5 | import * as Keys from './Keys'; 6 | export declare function cleanMetadataInput(input: any): MetadataBlock | undefined; 7 | export declare function filterMetadataInputs(input: any[]): MetadataBlock[]; 8 | export interface UrlInfo { 9 | id: Keys.PublicId; 10 | buffer: Buffer; 11 | type: string; 12 | } 13 | interface FileBlock { 14 | id: HyperfileId; 15 | bytes: number; 16 | mimeType: string; 17 | } 18 | export declare type MetadataBlock = FileBlock; 19 | export declare function isValidID(id: Keys.PublicId): id is Keys.PublicId; 20 | export declare function validateURL(urlString: BaseUrl | Keys.PublicId): UrlInfo; 21 | export declare function validateFileURL(urlString: HyperfileUrl | HyperfileId): HyperfileId; 22 | export declare function validateDocURL(urlString: DocUrl | DocId): DocId; 23 | export declare class Metadata { 24 | private files; 25 | private mimeTypes; 26 | readyQ: Queue<() => void>; 27 | private writable; 28 | private ready; 29 | private replay; 30 | private ledger; 31 | constructor(storageFn: Function); 32 | private loadLedger; 33 | private batchAdd; 34 | private writeThrough; 35 | private append; 36 | private addBlock; 37 | isWritable(actorId: ActorId): boolean; 38 | setWritable(actor: ActorId, writable: boolean): void; 39 | addFile(hyperfileUrl: HyperfileUrl, bytes: number, mimeType: string): void; 40 | addBlocks(blocks: MetadataBlock[]): void; 41 | isFile(id: HyperfileId | DocId): id is HyperfileId; 42 | isDoc(id: DocId | HyperfileId): id is DocId; 43 | bench(msg: string, f: () => void): void; 44 | fileMetadata(id: HyperfileId): PublicFileMetadata; 45 | } 46 | export declare type PublicMetadata = PublicDocMetadata | PublicFileMetadata; 47 | export declare type PublicDocMetadata = { 48 | type: 'Document'; 49 | clock: Clock; 50 | history: number; 51 | actor: ActorId | undefined; 52 | actors: ActorId[]; 53 | }; 54 | export declare type PublicFileMetadata = { 55 | type: 'File'; 56 | bytes: number; 57 | mimeType: string; 58 | }; 59 | export {}; 60 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | exports.Crypto = void 0; 23 | var Repo_1 = require("./Repo"); 24 | Object.defineProperty(exports, "Repo", { enumerable: true, get: function () { return Repo_1.Repo; } }); 25 | var Handle_1 = require("./Handle"); 26 | Object.defineProperty(exports, "Handle", { enumerable: true, get: function () { return Handle_1.Handle; } }); 27 | var RepoBackend_1 = require("./RepoBackend"); 28 | Object.defineProperty(exports, "RepoBackend", { enumerable: true, get: function () { return RepoBackend_1.RepoBackend; } }); 29 | var RepoFrontend_1 = require("./RepoFrontend"); 30 | Object.defineProperty(exports, "RepoFrontend", { enumerable: true, get: function () { return RepoFrontend_1.RepoFrontend; } }); 31 | var DocBackend_1 = require("./DocBackend"); 32 | Object.defineProperty(exports, "DocBackend", { enumerable: true, get: function () { return DocBackend_1.DocBackend; } }); 33 | var DocFrontend_1 = require("./DocFrontend"); 34 | Object.defineProperty(exports, "DocFrontend", { enumerable: true, get: function () { return DocFrontend_1.DocFrontend; } }); 35 | var CryptoClient_1 = require("./CryptoClient"); 36 | Object.defineProperty(exports, "CryptoClient", { enumerable: true, get: function () { return CryptoClient_1.CryptoClient; } }); 37 | const Crypto = __importStar(require("./Crypto")); 38 | exports.Crypto = Crypto; 39 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/Keys.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | exports.encode = exports.decode = exports.encodePair = exports.decodePair = exports.createBuffer = exports.create = exports.discoveryKey = void 0; 23 | const Base58 = __importStar(require("bs58")); 24 | const crypto = __importStar(require("hypercore-crypto")); 25 | const hypercore_crypto_1 = require("hypercore-crypto"); 26 | Object.defineProperty(exports, "discoveryKey", { enumerable: true, get: function () { return hypercore_crypto_1.discoveryKey; } }); 27 | function create() { 28 | return encodePair(crypto.keyPair()); 29 | } 30 | exports.create = create; 31 | function createBuffer() { 32 | return crypto.keyPair(); 33 | } 34 | exports.createBuffer = createBuffer; 35 | function decodePair(keys) { 36 | return { 37 | publicKey: decode(keys.publicKey), 38 | secretKey: keys.secretKey ? decode(keys.secretKey) : undefined, 39 | }; 40 | } 41 | exports.decodePair = decodePair; 42 | function encodePair(keys) { 43 | return { 44 | publicKey: encode(keys.publicKey), 45 | secretKey: keys.secretKey ? encode(keys.secretKey) : undefined, 46 | }; 47 | } 48 | exports.encodePair = encodePair; 49 | function decode(key) { 50 | return Base58.decode(key); 51 | } 52 | exports.decode = decode; 53 | function encode(key) { 54 | return Base58.encode(key); 55 | } 56 | exports.encode = encode; 57 | //# sourceMappingURL=Keys.js.map -------------------------------------------------------------------------------- /dist/CursorStore.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"CursorStore.js","sourceRoot":"","sources":["../src/CursorStore.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /tests/FileServer.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { testRepoPair, generateServerPath } from './misc' 3 | import * as Stream from '../src/StreamLogic' 4 | import { HyperfileUrl } from '../src' 5 | 6 | test('FileServer', (t) => { 7 | const [repoA, repoB] = testRepoPair() 8 | 9 | repoA.startFileServer(generateServerPath()) 10 | repoB.startFileServer(generateServerPath()) 11 | 12 | t.test('write and immediately read hyperfile', async (t) => { 13 | t.plan(2) 14 | 15 | const bufferA = Buffer.alloc(1024 * 1024, 1) 16 | const headerA = await repoA.files.write(Stream.fromBuffer(bufferA), 'application/octet-stream') 17 | 18 | const [headerB, stream] = await repoA.files.read(headerA.url) 19 | const bufferB = await Stream.toBuffer(stream) 20 | 21 | t.deepEqual(headerB, headerA, 'repoA gets written header') 22 | t.deepEqual(bufferB, bufferA, 'repoA gets written data') 23 | }) 24 | 25 | t.test('read header of new hyperfile', async (t) => { 26 | t.plan(2) 27 | 28 | const bufferA = Buffer.alloc(1024 * 1024, 1) 29 | const headerA = await repoA.files.write(Stream.fromBuffer(bufferA), 'application/octet-stream') 30 | 31 | const [headerB, stream] = await repoB.files.read(headerA.url) 32 | const bufferB = await Stream.toBuffer(stream) 33 | 34 | t.deepEqual(headerB, headerA, 'repoB gets header from repoA') 35 | t.deepEqual(bufferB, bufferA, 'repoB gets data from repoA') 36 | }) 37 | 38 | t.test('invalid URL responds with 404', async (t) => { 39 | t.plan(1) 40 | 41 | const fakeUrl = '3HDZs55bKCu3ULKy7KqSMceG3mzhefSujEM3v8va26eN' as HyperfileUrl 42 | 43 | try { 44 | await repoA.files.read(fakeUrl) 45 | } catch (e) { 46 | t.equal(e.message, 'Server error, code=404 message=Not Found', 'request errors') 47 | } 48 | }) 49 | 50 | t.test('short timeout causes socket hang-up', async (t) => { 51 | t.plan(1) 52 | 53 | const fakeUrl = 'hyperfile:/3HDZs55bKCu3ULKy7KqSMceG3mzhefSujEM3v8va26eN' as HyperfileUrl 54 | ;(repoA.back as any).fileServer.http.setTimeout(100) 55 | 56 | try { 57 | await repoA.files.read(fakeUrl) 58 | } catch (e) { 59 | t.equal(e.message, 'socket hang up', 'request times out') 60 | } finally { 61 | ;(repoA.back as any).fileServer.http.setTimeout(0) 62 | } 63 | }) 64 | 65 | test.onFinish(() => { 66 | repoA.close() 67 | repoB.close() 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /tests/FileStore.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { createHash } from 'crypto' 3 | import { testStorageFn, testDb } from './misc' 4 | import FileStore, { Header } from '../src/FileStore' 5 | import FeedStore from '../src/FeedStore' 6 | import * as Stream from '../src/StreamLogic' 7 | 8 | test('FileStore', (t) => { 9 | const feeds = new FeedStore(testDb(), testStorageFn()) 10 | const files = new FileStore(feeds) 11 | 12 | t.test('writing and reading 1MB file', async (t) => { 13 | t.plan(2) 14 | 15 | const testBuffer = Buffer.alloc(1024 * 1024, 1) 16 | const testStream = Stream.fromBuffer(testBuffer) 17 | const header = await files.write(testStream, 'application/octet-stream') 18 | const sha256 = createHash('sha256') 19 | .update(testBuffer) 20 | .digest('hex') 21 | 22 | const expectedHeader: Header = { 23 | size: testBuffer.length, 24 | mimeType: 'application/octet-stream', 25 | sha256, 26 | blocks: 17, 27 | url: header.url, 28 | } 29 | 30 | t.deepEqual(header, expectedHeader, 'reads the expected header') 31 | 32 | const output = await files.read(header.url) 33 | const outputBuffer = await Stream.toBuffer(output) 34 | 35 | t.deepEqual(outputBuffer, testBuffer, 'reads the written buffer') 36 | }) 37 | 38 | t.test('writing and reading stream of small chunks', async (t) => { 39 | t.plan(2) 40 | 41 | const testBuffers = [ 42 | Buffer.alloc(30 * 1024, 1), 43 | Buffer.alloc(30 * 1024, 2), 44 | Buffer.alloc(30 * 1024, 3), 45 | Buffer.alloc(30 * 1024, 4), 46 | Buffer.alloc(30 * 1024, 5), 47 | ] 48 | 49 | const testBuffer = Buffer.concat(testBuffers) 50 | const testStream = Stream.fromBuffers(testBuffers) 51 | const header = await files.write(testStream, 'application/octet-stream') 52 | const sha256 = createHash('sha256') 53 | .update(testBuffer) 54 | .digest('hex') 55 | 56 | const expectedHeader: Header = { 57 | size: testBuffer.length, 58 | mimeType: 'application/octet-stream', 59 | sha256, 60 | blocks: 3, 61 | url: header.url, 62 | } 63 | 64 | t.deepEqual(header, expectedHeader, 'reads the expected header') 65 | 66 | const output = await files.read(header.url) 67 | const outputBuffer = await Stream.toBuffer(output) 68 | 69 | t.deepEqual(outputBuffer, testBuffer, 'reads the written buffer') 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /examples/chat/src/channel.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { Doc } from 'automerge' 4 | import { EventEmitter } from 'events' 5 | import { DocUrl, Repo } from 'hypermerge' 6 | import Hyperswarm from 'hyperswarm' 7 | 8 | interface ChatChannel { 9 | messages: { 10 | [time: string]: Message 11 | } 12 | } 13 | 14 | interface Message { 15 | content?: string 16 | joined?: boolean 17 | nick: string 18 | } 19 | 20 | class Channel extends EventEmitter { 21 | public readonly nick: string 22 | public readonly url: DocUrl 23 | 24 | private _doc?: Doc 25 | private repo: Repo 26 | private swarm?: any 27 | 28 | constructor(nick: string, channelKey?: string) { 29 | super() 30 | 31 | // It's normal for a chat channel with a lot of participants 32 | // to have a lot of connections, so increase the limit to 33 | // avoid warnings about emitter leaks 34 | this.setMaxListeners(100) 35 | this.nick = nick 36 | this.swarm = Hyperswarm({ queue: { multiplex: true } }) 37 | 38 | // (Note that { memory: true } means none of this will be persisted to disk.) 39 | this.repo = new Repo({ memory: true }) 40 | 41 | this.repo.addSwarm(this.swarm, { announce: true }) 42 | 43 | if (!channelKey) { 44 | this.url = this.repo.create({ messages: {} }) 45 | } else { 46 | console.log(`Searching for chat channel ${channelKey} on network...`) 47 | this.url = channelKey as DocUrl 48 | } 49 | } 50 | 51 | get doc(): Doc | undefined { 52 | return this._doc 53 | } 54 | 55 | public ready() { 56 | this.repo.watch(this.url, (state: any) => { 57 | this._doc = state 58 | this.emit('updated') 59 | }) 60 | 61 | this.repo.change(this.url, (state) => { 62 | state.messages[Date.now()] = { 63 | joined: true, 64 | nick: this.nick, 65 | } 66 | }) 67 | 68 | this.emit('ready') 69 | } 70 | 71 | public addMessageToDoc(line: string) { 72 | const message = line.trim() 73 | if (message.length > 0) { 74 | this.repo.change(this.url, (state) => { 75 | state.messages[Date.now()] = { 76 | content: message, 77 | nick: this.nick, 78 | } 79 | }) 80 | } 81 | } 82 | 83 | public getNumConnections() { 84 | return this.swarm.peers 85 | } 86 | } 87 | 88 | export default Channel 89 | -------------------------------------------------------------------------------- /dist/MessageBus.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | const Queue_1 = __importDefault(require("./Queue")); 26 | const JsonBuffer = __importStar(require("./JsonBuffer")); 27 | class MessageBus { 28 | constructor(stream, subscriber) { 29 | this.onData = (data) => { 30 | this.receiveQ.push(JsonBuffer.parse(data)); 31 | }; 32 | this.stream = stream; 33 | this.sendQ = new Queue_1.default('MessageBus:sendQ'); 34 | this.receiveQ = new Queue_1.default('MessageBus:receiveQ'); 35 | this.stream 36 | .on('data', this.onData) 37 | .once('close', () => this.close()) 38 | .once('error', () => this.close()); 39 | this.sendQ.subscribe((msg) => { 40 | this.stream.write(JsonBuffer.bufferify(msg)); 41 | }); 42 | if (subscriber) 43 | this.subscribe(subscriber); 44 | } 45 | send(msg) { 46 | this.sendQ.push(msg); 47 | } 48 | subscribe(onMsg) { 49 | this.receiveQ.subscribe(onMsg); 50 | } 51 | unsubscribe() { 52 | this.receiveQ.unsubscribe(); 53 | } 54 | close() { 55 | this.sendQ.unsubscribe(); 56 | this.receiveQ.unsubscribe(); 57 | this.stream.end(); 58 | } 59 | } 60 | exports.default = MessageBus; 61 | //# sourceMappingURL=MessageBus.js.map -------------------------------------------------------------------------------- /dist/Block.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | exports.unpack = exports.pack = void 0; 23 | const zlib = __importStar(require("zlib")); 24 | const JsonBuffer = __importStar(require("./JsonBuffer")); 25 | const BROTLI = 'BR'; 26 | const { BROTLI_PARAM_MODE, BROTLI_MODE_TEXT, BROTLI_PARAM_SIZE_HINT, BROTLI_PARAM_QUALITY, } = zlib.constants; 27 | function pack(obj) { 28 | const blockHeader = Buffer.from(BROTLI); 29 | const source = JsonBuffer.bufferify(obj); 30 | const blockBody = Buffer.from(zlib.brotliCompressSync(source, { 31 | params: { 32 | [BROTLI_PARAM_MODE]: BROTLI_MODE_TEXT, 33 | [BROTLI_PARAM_SIZE_HINT]: source.length, 34 | [BROTLI_PARAM_QUALITY]: 11, 35 | }, 36 | })); 37 | if (source.length < blockBody.length) { 38 | return source; 39 | } 40 | else { 41 | return Buffer.concat([blockHeader, blockBody]); 42 | } 43 | } 44 | exports.pack = pack; 45 | function unpack(data) { 46 | //if (data.slice(0,2).toString() === '{"') { // an old block before we added compression 47 | const header = data.slice(0, 2); 48 | switch (header.toString()) { 49 | case '{"': 50 | return JsonBuffer.parse(data); 51 | case BROTLI: 52 | return JsonBuffer.parse(Buffer.from(zlib.brotliDecompressSync(data.slice(2)))); 53 | default: 54 | throw new Error(`fail to unpack blocks - head is '${header}'`); 55 | } 56 | } 57 | exports.unpack = unpack; 58 | //# sourceMappingURL=Block.js.map -------------------------------------------------------------------------------- /dist/RepoFrontend.d.ts: -------------------------------------------------------------------------------- 1 | import Queue from './Queue'; 2 | import MapSet from './MapSet'; 3 | import { ToBackendQueryMsg, ToBackendRepoMsg, ToFrontendRepoMsg } from './RepoMsg'; 4 | import { Handle } from './Handle'; 5 | import { Doc, Patch, ChangeFn } from 'automerge'; 6 | import { DocFrontend } from './DocFrontend'; 7 | import { Clock } from './Clock'; 8 | import { PublicMetadata } from './Metadata'; 9 | import { DocUrl, DocId, ActorId, HyperfileId, HyperfileUrl } from './Misc'; 10 | import FileServerClient from './FileServerClient'; 11 | import { CryptoClient } from './CryptoClient'; 12 | import { Crawler } from './Crawler'; 13 | export interface DocMetadata { 14 | clock: Clock; 15 | history: number; 16 | actor?: ActorId; 17 | } 18 | export interface ProgressEvent { 19 | actor: ActorId; 20 | index: number; 21 | size: number; 22 | time: number; 23 | } 24 | export declare class RepoFrontend { 25 | toBackend: Queue; 26 | docs: Map>; 27 | cb: Map void>; 28 | msgcb: Map void>; 29 | readFiles: MapSet void>; 30 | files: FileServerClient; 31 | crypto: CryptoClient; 32 | crawler: Crawler; 33 | constructor(); 34 | create: (init?: T | undefined) => DocUrl; 35 | change: (url: DocUrl, fn: ChangeFn) => void; 36 | meta: (url: DocUrl | HyperfileUrl, cb: (meta: PublicMetadata | undefined) => void) => void; 37 | meta2: (url: DocUrl | HyperfileUrl) => DocMetadata | undefined; 38 | merge: (url: DocUrl, target: DocUrl) => void; 39 | fork: (url: DocUrl) => DocUrl; 40 | watch: (url: DocUrl, cb: (val: import("automerge").FreezeObject, clock?: Clock | undefined, index?: number | undefined) => void) => Handle; 41 | message: (url: DocUrl, contents: T) => void; 42 | doc: (url: DocUrl, cb?: ((val: import("automerge").FreezeObject, clock?: Clock | undefined) => void) | undefined) => Promise>; 43 | materialize: (url: DocUrl, history: number, cb: (val: import("automerge").FreezeObject) => void) => void; 44 | queryBackend: (query: ToBackendQueryMsg, cb: (arg: any) => void) => void; 45 | open: (url: DocUrl, crawl?: boolean) => Handle; 46 | debug(url: DocUrl): void; 47 | private openDocFrontend; 48 | subscribe: (subscriber: (message: ToBackendRepoMsg) => void) => void; 49 | close: () => void; 50 | destroy: (url: DocUrl) => void; 51 | receive: (msg: ToFrontendRepoMsg) => void; 52 | } 53 | -------------------------------------------------------------------------------- /src/FileStore.ts: -------------------------------------------------------------------------------- 1 | import { HyperfileUrl } from './Misc' 2 | import { Readable } from 'stream' 3 | import FeedStore, { FeedId } from './FeedStore' 4 | import { validateFileURL } from './Metadata' 5 | import * as Keys from './Keys' 6 | import * as JsonBuffer from './JsonBuffer' 7 | import Queue from './Queue' 8 | import { ChunkSizeTransform, HashPassThrough } from './StreamLogic' 9 | 10 | export const BLOCK_SIZE = 62 * 1024 11 | 12 | export interface Header { 13 | url: HyperfileUrl 14 | size: number 15 | blocks: number 16 | mimeType: string 17 | sha256: string 18 | } 19 | 20 | export default class FileStore { 21 | private feeds: FeedStore 22 | writeLog: Queue
23 | 24 | constructor(store: FeedStore) { 25 | this.feeds = store 26 | this.writeLog = new Queue('FileStore:writeLog') 27 | } 28 | 29 | async header(url: HyperfileUrl): Promise
{ 30 | return this.feeds.head(toFeedId(url)).then(JsonBuffer.parse) 31 | } 32 | 33 | async read(url: HyperfileUrl): Promise { 34 | const feedId = toFeedId(url) 35 | return this.feeds.stream(feedId, 0, -1) 36 | } 37 | 38 | async write(stream: Readable, mimeType: string): Promise
{ 39 | const keys = Keys.create() 40 | const feedId = await this.feeds.create(keys) 41 | 42 | const appendStream = await this.feeds.appendStream(feedId) 43 | 44 | return new Promise
((res, rej) => { 45 | const chunkStream = new ChunkSizeTransform(BLOCK_SIZE) 46 | const hashStream = new HashPassThrough('sha256') 47 | 48 | stream 49 | .pipe(hashStream) 50 | .pipe(chunkStream) 51 | .pipe(appendStream) 52 | .on('error', (err) => rej(err)) 53 | .on('finish', async () => { 54 | const header: Header = { 55 | url: toHyperfileUrl(feedId), 56 | mimeType, 57 | size: chunkStream.processedBytes, 58 | blocks: chunkStream.chunkCount, 59 | sha256: hashStream.hash.digest('hex'), 60 | } 61 | 62 | await this.feeds.append(feedId, JsonBuffer.bufferify(header)) 63 | this.writeLog.push(header) 64 | res(header) 65 | }) 66 | }) 67 | } 68 | } 69 | 70 | export function isHyperfileUrl(url: string): url is HyperfileUrl { 71 | return /^hyperfile:\/\w+$/.test(url) 72 | } 73 | 74 | function toHyperfileUrl(feedId: FeedId): HyperfileUrl { 75 | return `hyperfile:/${feedId}` as HyperfileUrl 76 | } 77 | 78 | function toFeedId(hyperfileUrl: HyperfileUrl): FeedId { 79 | return (validateFileURL(hyperfileUrl) as string) as FeedId 80 | } 81 | -------------------------------------------------------------------------------- /dist/PeerConnection.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"PeerConnection.js","sourceRoot":"","sources":["../src/PeerConnection.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/Handle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Handle.js","sourceRoot":"","sources":["../src/Handle.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /tools/cli.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { Repo, HyperfileUrl } from '../src' 3 | import mime from 'mime-types' 4 | 5 | import Hyperswarm from 'hyperswarm' 6 | 7 | const { program } = require('commander') 8 | 9 | program.version('1.0').option('-r, --repo ', "specify the repo's location", '.data') 10 | 11 | program 12 | .command('store ') 13 | .description('copy a file into a hyperfile') 14 | .option('-m, --mime-type', 'override the mime-type') 15 | .action(cp) 16 | 17 | program 18 | .command('cat ') 19 | .description('read a hyperfile out to stdout') 20 | .action(cat) 21 | 22 | program.parse(process.argv) 23 | 24 | function repoSetup(repoPath: string) { 25 | if (!fs.existsSync(repoPath + '/ledger')) { 26 | console.log('No repo found: ' + repoPath) 27 | process.exit() 28 | } 29 | 30 | const repo = new Repo({ path: repoPath }) 31 | 32 | // ARGH 33 | repo.startFileServer('stupid-socket-path.sock') 34 | 35 | console.log('going to', repoPath) 36 | repo.setSwarm(Hyperswarm()) 37 | return repo 38 | } 39 | 40 | function cp(file: string, options: any) { 41 | console.log(options) 42 | 43 | const repo = repoSetup(program.repo) 44 | 45 | if (!fs.existsSync(file)) { 46 | console.log('No file found: ' + file) 47 | process.exit() 48 | } 49 | 50 | const mimeType = mime.lookup(file) || program.mimeType || 'application/octet-stream' 51 | 52 | console.log('file', file, 'mimetype', mimeType) 53 | const fileStream = fs.createReadStream(file) 54 | console.log('writing to', repo.files) 55 | repo.files.write(fileStream, mimeType).then(({ url }) => { 56 | console.log('written') 57 | repo.files.read(url).then((header) => { 58 | console.log(url) 59 | console.log(header) 60 | //repo.close() 61 | setTimeout(() => { 62 | process.exit() 63 | // I need this to make sure the ledger finishes its append 64 | // get repo.close() to work and flush correctly? 65 | }, 1000) 66 | }) 67 | }) 68 | } 69 | 70 | async function cat(url: HyperfileUrl, options: any) { 71 | console.log(options) 72 | 73 | const repo = repoSetup(program.repo) 74 | 75 | const [header, stream] = await repo.files.read(url) 76 | // Read and disply the file data on console 77 | console.log(header.mimeType) 78 | stream.on('data', function(chunk) { 79 | console.log(chunk.toString()) 80 | }) 81 | 82 | //repo.close() 83 | setTimeout(() => { 84 | process.exit() 85 | // I need this to make sure the ledger finishes its append 86 | // get repo.close() to work and flush correctly? 87 | }, 1000) 88 | } 89 | 90 | setTimeout(() => {}, 50000) // dont exit yet 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hypermerge", 3 | "version": "2.0.0-beta.25", 4 | "description": "Node.js library for building p2p collaborative applications without server infrastructure", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "build": "rm -rd ./dist/* && tsc", 8 | "postbuild": "copyfiles -f src/migrations/*.sql dist/migrations", 9 | "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'", 10 | "peek": "ts-node --files tools/Peek.ts", 11 | "cli": "ts-node --files tools/cli.ts", 12 | "watch": "ts-node --files tools/Watch.ts", 13 | "serve": "ts-node --files tools/Serve.ts", 14 | "meta": "ts-node --files tools/Meta.ts", 15 | "cat": "ts-node --files tools/Cat.ts", 16 | "tape": "ts-node --files node_modules/tape/bin/tape tests/*.test.ts", 17 | "tape-only": "ts-node --files node_modules/tape/bin/tape", 18 | "tape-inspect": "node --inspect -r ts-node/register/transpile-only node_modules/tape/bin/tape" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/inkandswitch/hypermerge.git" 23 | }, 24 | "author": "", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/inkandswitch/hypermerge/issues" 28 | }, 29 | "homepage": "https://github.com/inkandswitch/hypermerge#readme", 30 | "dependencies": { 31 | "automerge": "github:automerge/automerge#opaque-strings", 32 | "better-sqlite3": "^5.4.3", 33 | "bs58": "^4.0.1", 34 | "copyfiles": "^2.1.1", 35 | "debug": "^4.1.1", 36 | "hypercore": "^8.2.5", 37 | "hypercore-crypto": "^1.0.0", 38 | "hypercore-protocol": "^7.6.0", 39 | "js-sha1": "^0.6.0", 40 | "mime-types": "^2.1.24", 41 | "noise-peer": "^1.1.0", 42 | "proper-lockfile": "^4.1.1", 43 | "pump": "^3.0.0", 44 | "random-access-file": "^2.1.3", 45 | "random-access-memory": "^3.0.0", 46 | "simple-message-channels": "^1.2.1", 47 | "sodium-native": "^2.4.6", 48 | "streamx": "^2.5.0", 49 | "uuid": "^3.3.3" 50 | }, 51 | "devDependencies": { 52 | "@types/better-sqlite3": "^5.4.0", 53 | "@types/debug": "^0.0.31", 54 | "@types/mime-types": "^2.1.0", 55 | "@types/node": "^12.12.6", 56 | "@types/proper-lockfile": "^4.1.1", 57 | "@types/pump": "^1.1.0", 58 | "@types/tape": "^4.2.32", 59 | "@types/uuid": "^3.4.5", 60 | "@types/ws": "^6.0.3", 61 | "commander": "^5.1.0", 62 | "husky": "^3.0.5", 63 | "hyperswarm": "^2.3.1", 64 | "prettier": "^1.19.1", 65 | "tape": "^4.11.0", 66 | "ts-node": "^8.3.0", 67 | "typescript": "^3.7.2" 68 | }, 69 | "resolutions": { 70 | "sodium-native": "^2.4.6", 71 | "memory-pager": "1.4.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /dist/NetworkPeer.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"NetworkPeer.js","sourceRoot":"","sources":["../src/NetworkPeer.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/Repo.ts: -------------------------------------------------------------------------------- 1 | import { Options, RepoBackend } from './RepoBackend' 2 | import { RepoFrontend } from './RepoFrontend' 3 | import { Handle } from './Handle' 4 | import { PublicMetadata } from './Metadata' 5 | import { Clock } from './Clock' 6 | import { DocUrl, HyperfileUrl, RepoId } from './Misc' 7 | import FileServerClient from './FileServerClient' 8 | import { Swarm, JoinOptions } from './SwarmInterface' 9 | import { Doc, Proxy } from 'automerge' 10 | import { CryptoClient } from './CryptoClient' 11 | 12 | export class Repo { 13 | front: RepoFrontend 14 | back: RepoBackend 15 | 16 | id: RepoId 17 | create: (init?: T) => DocUrl 18 | open: (id: DocUrl) => Handle 19 | destroy: (id: DocUrl) => void 20 | //follow: (id: string, target: string) => void; 21 | /** @deprecated Use addSwarm */ 22 | setSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void 23 | 24 | addSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void 25 | removeSwarm: (swarm: Swarm, joinOptions?: JoinOptions) => void 26 | 27 | message: (url: DocUrl, message: any) => void 28 | 29 | crypto: CryptoClient 30 | 31 | files: FileServerClient 32 | startFileServer: (fileServerPath: string) => void 33 | 34 | fork: (url: DocUrl) => DocUrl 35 | watch: (url: DocUrl, cb: (val: Doc, clock?: Clock, index?: number) => void) => Handle 36 | doc: (url: DocUrl, cb?: (val: Doc, clock?: Clock) => void) => Promise> 37 | merge: (url: DocUrl, target: DocUrl) => void 38 | change: (url: DocUrl, fn: (state: Proxy) => void) => void 39 | materialize: (url: DocUrl, seq: number, cb: (val: Doc) => void) => void 40 | meta: (url: DocUrl | HyperfileUrl, cb: (meta: PublicMetadata | undefined) => void) => void 41 | close: () => void 42 | 43 | constructor(opts: Options) { 44 | this.back = new RepoBackend(opts) 45 | this.front = new RepoFrontend() 46 | this.front.subscribe(this.back.receive) 47 | this.back.subscribe(this.front.receive) 48 | this.id = this.back.id 49 | this.create = this.front.create 50 | this.open = this.front.open 51 | this.message = this.front.message 52 | this.destroy = this.front.destroy 53 | this.meta = this.front.meta 54 | this.doc = this.front.doc 55 | this.fork = this.front.fork 56 | this.close = this.front.close 57 | this.change = this.front.change 58 | this.files = this.front.files 59 | this.watch = this.front.watch 60 | this.merge = this.front.merge 61 | this.setSwarm = this.back.setSwarm 62 | this.addSwarm = this.back.addSwarm 63 | this.removeSwarm = this.back.removeSwarm 64 | this.startFileServer = this.back.startFileServer 65 | this.materialize = this.front.materialize 66 | this.crypto = this.front.crypto 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dist/FeedStore.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import { Readable, Writable } from 'stream'; 4 | import { Feed as HypercoreFeed } from 'hypercore'; 5 | import { KeyPair, PublicId, DiscoveryId } from './Keys'; 6 | import Queue from './Queue'; 7 | import { Database } from './SqlDatabase'; 8 | import * as Crypto from './Crypto'; 9 | export declare type Feed = HypercoreFeed; 10 | export declare type FeedId = PublicId; 11 | export declare type Block = Uint8Array; 12 | interface StorageFn { 13 | (path: string): (filename: string) => unknown; 14 | } 15 | /** 16 | * Note: 17 | * FeedId should really be the discovery key. The public key should be 18 | * PublicId. Reading and writing to existing hypercores does not require the 19 | * public key; it's saved to disk. In a future refactor, we plan to remove the 20 | * reliance on public keys, and instead only provide the public key when 21 | * creating a new hypercore, or opening an unknown hypercore. The ledger keeps 22 | * track of which hypercores have already been opened. 23 | */ 24 | export default class FeedStore { 25 | private storage; 26 | private loaded; 27 | info: FeedInfoStore; 28 | constructor(db: Database, storageFn: StorageFn); 29 | /** 30 | * Create a brand-new writable feed using the given key pair. 31 | * Promises the FeedId. 32 | */ 33 | create(keys: Required): Promise; 34 | sign(feedId: FeedId, message: Buffer): Promise>; 35 | verify(feedId: FeedId, signedMessage: Crypto.SignedMessage): boolean; 36 | append(feedId: FeedId, ...blocks: Block[]): Promise; 37 | appendStream(feedId: FeedId): Promise; 38 | read(feedId: FeedId, seq: number): Promise; 39 | head(feedId: FeedId): Promise; 40 | stream(feedId: FeedId, start?: number, end?: number): Promise; 41 | closeFeed(feedId: FeedId): Promise; 42 | close(): Promise; 43 | getFeed(feedId: FeedId): Promise; 44 | private open; 45 | private openOrCreateFeed; 46 | } 47 | export interface FeedInfo { 48 | publicId: PublicId; 49 | discoveryId: DiscoveryId; 50 | isWritable: 0 | 1; 51 | } 52 | export declare class FeedInfoStore { 53 | createdQ: Queue; 54 | private prepared; 55 | constructor(db: Database); 56 | save(info: FeedInfo): void; 57 | getPublicId(discoveryId: DiscoveryId): PublicId | undefined; 58 | hasDiscoveryId(discoveryId: DiscoveryId): boolean; 59 | byPublicId(publicId: PublicId): FeedInfo | undefined; 60 | byDiscoveryId(discoveryId: DiscoveryId): FeedInfo | undefined; 61 | allPublicIds(): PublicId[]; 62 | allDiscoveryIds(): DiscoveryId[]; 63 | } 64 | export {}; 65 | -------------------------------------------------------------------------------- /tests/Network.test.ts: -------------------------------------------------------------------------------- 1 | import test from 'tape' 2 | import { testSwarm, testDiscoveryId, testNetwork, eachCall } from './misc' 3 | import NetworkPeer, { PeerId } from '../src/NetworkPeer' 4 | 5 | interface TestMsg { 6 | senderId: PeerId 7 | } 8 | 9 | test('Network', (t) => { 10 | t.plan(5) 11 | 12 | const topic = testDiscoveryId() 13 | 14 | const netA = testNetwork() 15 | const netB = testNetwork() 16 | 17 | netA.addSwarm(testSwarm()) 18 | netB.addSwarm(testSwarm()) 19 | 20 | netA.peerQ.subscribe((peer) => { 21 | t.isEqual(peer.id, netB.selfId, 'netA finds netB') 22 | t.assert(netA.discovered.size === 1, 'netA records a discovery') 23 | 24 | peer.connection?.openBus('TestMsg', (msg) => { 25 | t.deepEqual(msg, { senderId: netB.selfId }, 'netA gets message from netB') 26 | }) 27 | }) 28 | 29 | netB.peerQ.subscribe((peer) => { 30 | t.isEqual(peer.id, netA.selfId, 'netB finds netA') 31 | t.assert(netB.discovered.size === 1, 'netB records a discovery') 32 | 33 | peer.connection?.openBus('TestMsg').send({ senderId: netB.selfId }) 34 | }) 35 | 36 | netA.join(topic) 37 | netB.join(topic) 38 | 39 | test.onFinish(() => { 40 | netA.close() 41 | netB.close() 42 | }) 43 | }) 44 | 45 | test('Re-connecting peers after connection broken', (t) => { 46 | t.plan(6) 47 | 48 | const topic = testDiscoveryId() 49 | 50 | const netA = testNetwork() 51 | const netB = testNetwork() 52 | 53 | netA.addSwarm(testSwarm()) 54 | netB.addSwarm(testSwarm()) 55 | 56 | netA.join(topic) 57 | netB.join(topic) 58 | 59 | netA.peerQ.subscribe( 60 | eachCall([ 61 | (peerA) => { 62 | t.pass('peerA gets first connection') 63 | if (!peerA.weHaveAuthority) delayedClose(peerA) 64 | }, 65 | (peerA) => { 66 | t.pass('peerA gets second connection') 67 | if (peerA.weHaveAuthority) delayedClose(peerA) 68 | }, 69 | (_peerA) => { 70 | t.pass('peerA gets third connection') 71 | }, 72 | ]) 73 | ) 74 | 75 | netB.peerQ.subscribe( 76 | eachCall([ 77 | (peerB) => { 78 | t.pass('peerB gets first connection') 79 | if (!peerB.weHaveAuthority) delayedClose(peerB) 80 | }, 81 | (peerB) => { 82 | t.pass('peerB gets second connection') 83 | if (peerB.weHaveAuthority) delayedClose(peerB) 84 | }, 85 | (_peerB) => { 86 | t.pass('peerB gets third connection') 87 | }, 88 | ]) 89 | ) 90 | 91 | test.onFinish(() => { 92 | netA.close() 93 | netB.close() 94 | }) 95 | }) 96 | 97 | function delayedClose(peer: NetworkPeer) { 98 | setTimeout(() => { 99 | peer.connection?.close() 100 | }, 300) 101 | } 102 | -------------------------------------------------------------------------------- /dist/Misc.d.ts: -------------------------------------------------------------------------------- 1 | import { FeedId } from './FeedStore'; 2 | import { Key, PublicKey, DiscoveryKey, DiscoveryId, PublicId, KeyId } from './Keys'; 3 | export { DiscoveryId }; 4 | export declare type RepoId = PublicId & { 5 | __repoId: true; 6 | }; 7 | export declare type DocId = PublicId & { 8 | __docId: true; 9 | }; 10 | export declare type ActorId = FeedId & { 11 | __actorId: true; 12 | }; 13 | export declare type HyperfileId = FeedId & { 14 | __hyperfileId: true; 15 | }; 16 | export declare type BaseUrl = string & { 17 | __baseUrl: true; 18 | }; 19 | export declare type DocUrl = BaseUrl & { 20 | __docUrl: true; 21 | }; 22 | export declare type HyperfileUrl = BaseUrl & { 23 | __hyperfileUrl: true; 24 | }; 25 | export declare function decodeId(id: KeyId): Key; 26 | export declare function encodeRepoId(repoKey: PublicKey): RepoId; 27 | export declare function encodeDocId(actorKey: PublicKey): DocId; 28 | export declare function encodeActorId(actorKey: PublicKey): ActorId; 29 | export declare function encodeHyperfileId(hyperfileKey: PublicKey): HyperfileId; 30 | export declare function toDocUrl(docId: DocId): DocUrl; 31 | export declare function toHyperfileUrl(hyperfileId: HyperfileId): HyperfileUrl; 32 | export declare function toDiscoveryId(id: PublicId): DiscoveryId; 33 | export declare function toDiscoveryKey(id: PublicId): DiscoveryKey; 34 | export declare function rootActorId(docId: DocId): ActorId; 35 | export declare function hyperfileActorId(hyperfileId: HyperfileId): ActorId; 36 | export declare function isBaseUrl(str: BaseUrl | KeyId): str is BaseUrl; 37 | export declare function isDocUrl(str: string): str is DocUrl; 38 | export declare function withoutQuery(url: DocUrl): DocUrl; 39 | export declare function withoutQuery(url: HyperfileUrl): HyperfileUrl; 40 | export declare function withoutQuery(url: BaseUrl): BaseUrl; 41 | export declare function isString(val: unknown): val is string; 42 | export declare function isPlainObject(val: unknown): val is object; 43 | export declare function joinSets(sets: Set[]): Set; 44 | export declare function ID(_id: string): string; 45 | export declare function notEmpty(value: TValue | null | undefined): value is TValue; 46 | export declare function getOrCreate(map: WeakMap, key: K, create: (key: K) => V): V; 47 | export declare function getOrCreate(map: Map, key: K, create: (key: K) => V): V; 48 | /** 49 | * The returned promise resolves after the `resolver` fn is called `n` times. 50 | * Promises the last value passed to the resolver. 51 | */ 52 | export declare function createMultiPromise(n: number, factory: (resolver: (value: T) => void, rejector: (err: Error) => void) => void): Promise; 53 | export declare function toIpcPath(path: string): string; 54 | export declare function errorMessage(e: Error): string; 55 | -------------------------------------------------------------------------------- /dist/CryptoClient.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"CryptoClient.js","sourceRoot":"","sources":["../src/CryptoClient.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/FileServerClient.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"FileServerClient.js","sourceRoot":"","sources":["../src/FileServerClient.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/Crawler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | var __importDefault = (this && this.__importDefault) || function (mod) { 22 | return (mod && mod.__esModule) ? mod : { "default": mod }; 23 | }; 24 | Object.defineProperty(exports, "__esModule", { value: true }); 25 | exports.Crawler = void 0; 26 | const Debug_1 = __importDefault(require("./Debug")); 27 | const Misc_1 = require("./Misc"); 28 | const FileStore_1 = require("./FileStore"); 29 | const TraverseLogic = __importStar(require("./TraverseLogic")); 30 | const log = Debug_1.default('Crawler'); 31 | class Crawler { 32 | constructor(repo) { 33 | this.seen = new Set(); 34 | this.handles = new Map(); 35 | this.onUrl = (urlVal) => { 36 | const url = Misc_1.withoutQuery(urlVal); 37 | if (this.seen.has(url)) 38 | return; 39 | log(`Crawling ${url}`); 40 | if (Misc_1.isDocUrl(url)) { 41 | const handle = this.repo.open(url, false); 42 | this.seen.add(url); 43 | this.handles.set(url, handle); 44 | setImmediate(() => handle.subscribe(this.onDocumentUpdate)); 45 | } 46 | else if (FileStore_1.isHyperfileUrl(url)) { 47 | this.seen.add(url); 48 | setImmediate(() => this.repo.files.header(url)); 49 | } 50 | }; 51 | this.onDocumentUpdate = (doc) => { 52 | const urls = TraverseLogic.iterativeDfs(isHypermergeUrl, doc); 53 | urls.forEach(this.onUrl); 54 | }; 55 | this.repo = repo; 56 | } 57 | crawl(url) { 58 | log(`Crawling from root ${url}`); 59 | this.onUrl(url); 60 | } 61 | close() { 62 | this.handles.forEach((handle) => handle.close()); 63 | this.handles.clear(); 64 | this.seen.clear(); 65 | } 66 | } 67 | exports.Crawler = Crawler; 68 | function isHypermergeUrl(val) { 69 | if (!Misc_1.isString(val)) 70 | return false; 71 | return Misc_1.isDocUrl(val) || FileStore_1.isHyperfileUrl(val); 72 | } 73 | //# sourceMappingURL=Crawler.js.map -------------------------------------------------------------------------------- /src/types/sodium-native.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'sodium-native' { 2 | export type Key = Buffer & { __key: true } 3 | export type PublicKey = Key & { __publicKey: true } 4 | export type SecretKey = Key & { __secretKey: true } 5 | export interface KeyPair { 6 | publicKey: PublicKey 7 | secretKey: SecretKey 8 | } 9 | 10 | export type PublicSigningKey = PublicKey & { __publicSigningKey: true } 11 | export type SecretSigningKey = SecretKey & { __secretSigningKey: true } 12 | export interface SigningKeyPair extends KeyPair { 13 | publicKey: PublicSigningKey 14 | secretKey: SecretSigningKey 15 | } 16 | 17 | export type PublicEncryptionKey = PublicKey & { __publicEncryptionKey: true } 18 | export type SecretEncryptionKey = SecretKey & { __secretEncryptionKey: true } 19 | export interface EncryptionKeyPair extends KeyPair { 20 | publicKey: PublicEncryptionKey 21 | secretKey: SecretEncryptionKey 22 | } 23 | 24 | export type Signature = Buffer & { __signature: true } 25 | export type SealedBoxCiphertext = Buffer & { __sealedBoxCiphertext: true } 26 | export type BoxCiphertext = Buffer & { __boxCiphertext: true } 27 | export type BoxNonce = Buffer & { __nonce: true } 28 | 29 | export const crypto_sign_BYTES: number 30 | export const crypto_box_SEALBYTES: number 31 | export const crypto_sign_PUBLICKEYBYTES: number 32 | export const crypto_sign_SECRETKEYBYTES: number 33 | export const crypto_box_PUBLICKEYBYTES: number 34 | export const crypto_box_SECRETKEYBYTES: number 35 | export const crypto_box_NONCEBYTES: number 36 | export const crypto_box_MACBYTES: number 37 | 38 | export function randombytes_buf(buffer: Buffer): Buffer 39 | 40 | export function crypto_sign_keypair( 41 | publicKey: PublicSigningKey, 42 | secretKey: SecretSigningKey 43 | ): void 44 | 45 | export function crypto_box_keypair( 46 | publicKey: PublicEncryptionKey, 47 | secretKey: SecretEncryptionKey 48 | ): void 49 | 50 | export function crypto_sign_detached( 51 | signature: Signature, 52 | message: Buffer, 53 | secretKey: SecretSigningKey 54 | ): void 55 | 56 | export function crypto_sign_verify_detached( 57 | signature: Signature, 58 | message: Buffer, 59 | publicKey: PublicSigningKey 60 | ): boolean 61 | 62 | export function crypto_box_seal( 63 | ciphertext: SealedBoxCiphertext, 64 | message: Buffer, 65 | publicKey: PublicEncryptionKey 66 | ): void 67 | 68 | export function crypto_box_seal_open( 69 | message: Buffer, 70 | ciphertext: SealedBoxCiphertext, 71 | publicKey: PublicEncryptionKey, 72 | secretKey: SecretEncryptionKey 73 | ): boolean 74 | 75 | export function crypto_box_easy( 76 | ciphertext: BoxCiphertext, 77 | message: Buffer, 78 | nonce: BoxNonce, 79 | publicKey: PublicEncryptionKey, 80 | secretKey: SecretEncryptionKey 81 | ) 82 | 83 | export function crypto_box_open_easy( 84 | message: Buffer, 85 | ciphertext: BoxCiphertext, 86 | nonce: BoxNonce, 87 | publicKey: PublicEncryptionKey, 88 | secretKey: SecretEncryptionKey 89 | ): boolean 90 | } 91 | -------------------------------------------------------------------------------- /dist/Misc.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Misc.js","sourceRoot":"","sources":["../src/Misc.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/RepoBackend.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import Queue from './Queue'; 3 | import { Metadata } from './Metadata'; 4 | import { Actor } from './Actor'; 5 | import * as Clock from './Clock'; 6 | import { ToBackendQueryMsg, ToBackendRepoMsg, ToFrontendRepoMsg } from './RepoMsg'; 7 | import { Change } from 'automerge'; 8 | import * as DocBackend from './DocBackend'; 9 | import { ActorId, DocId, RepoId } from './Misc'; 10 | import FeedStore from './FeedStore'; 11 | import FileStore from './FileStore'; 12 | import Network from './Network'; 13 | import NetworkPeer from './NetworkPeer'; 14 | import { Swarm, JoinOptions } from './SwarmInterface'; 15 | import { PeerMsg } from './PeerMsg'; 16 | import ClockStore from './ClockStore'; 17 | import CursorStore from './CursorStore'; 18 | import MessageRouter from './MessageRouter'; 19 | import KeyStore from './KeyStore'; 20 | import ReplicationManager, { Discovery } from './ReplicationManager'; 21 | export interface FeedData { 22 | actorId: ActorId; 23 | writable: Boolean; 24 | changes: Change[]; 25 | } 26 | export interface Options { 27 | path?: string; 28 | memory?: boolean; 29 | } 30 | export declare class RepoBackend { 31 | path?: string; 32 | storage: Function; 33 | feeds: FeedStore; 34 | keys: KeyStore; 35 | files: FileStore; 36 | clocks: ClockStore; 37 | cursors: CursorStore; 38 | actors: Map; 39 | docs: Map; 40 | meta: Metadata; 41 | opts: Options; 42 | toFrontend: Queue; 43 | id: RepoId; 44 | network: Network; 45 | messages: MessageRouter; 46 | replication: ReplicationManager; 47 | lockRelease?: () => void; 48 | swarmKey: Buffer; 49 | private db; 50 | private fileServer; 51 | constructor(opts: Options); 52 | startFileServer: (path: string) => void; 53 | private create; 54 | localActorId(docId: DocId): ActorId | undefined; 55 | private debug; 56 | private open; 57 | merge(id: DocId, clock: Clock.Clock): void; 58 | close: () => Promise; 59 | private allReadyActors; 60 | private loadDocument; 61 | private getReadyActor; 62 | storageFn: (path: string) => (name: string) => any; 63 | initActorFeed(doc: DocBackend.DocBackend): ActorId; 64 | actorIds(doc: DocBackend.DocBackend): ActorId[]; 65 | docActors(doc: DocBackend.DocBackend): Actor[]; 66 | syncReadyActors: (ids: ActorId[]) => void; 67 | private getGoodClock; 68 | private documentNotify; 69 | onPeer: (peer: NetworkPeer) => void; 70 | onDiscovery: ({ feedId, peer }: Discovery) => void; 71 | private onMessage; 72 | private actorNotify; 73 | private initActor; 74 | syncChanges: (actor: Actor) => void; 75 | /** @deprecated Use addSwarm */ 76 | setSwarm: (swarm: Swarm, joinOptions?: JoinOptions | undefined) => void; 77 | addSwarm: (swarm: Swarm, joinOptions?: JoinOptions | undefined) => void; 78 | removeSwarm: (swarm: Swarm) => void; 79 | subscribe: (subscriber: (message: ToFrontendRepoMsg) => void) => void; 80 | handleQuery: (id: number, query: ToBackendQueryMsg) => Promise; 81 | receive: (msg: ToBackendRepoMsg) => void; 82 | actor(id: ActorId): Actor | undefined; 83 | } 84 | -------------------------------------------------------------------------------- /dist/Handle.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Handle = void 0; 4 | class Handle { 5 | constructor(repo, url) { 6 | this.state = null; 7 | this.clock = null; 8 | this.counter = 0; 9 | this.message = (contents) => { 10 | this.repo.message(this.url, contents); 11 | return this; 12 | }; 13 | this.push = (item, clock) => { 14 | this.state = item; 15 | this.clock = clock; 16 | if (this.subscription) { 17 | this.subscription(item, clock, this.counter++); 18 | } 19 | }; 20 | this.receiveProgressEvent = (progress) => { 21 | if (this.progressSubscription) { 22 | this.progressSubscription(progress); 23 | } 24 | }; 25 | this.receiveDocumentMessage = (contents) => { 26 | if (this.messageSubscription) { 27 | this.messageSubscription(contents); 28 | } 29 | }; 30 | this.once = (subscriber) => { 31 | this.subscribe((doc, clock, index) => { 32 | subscriber(doc, clock, index); 33 | this.close(); 34 | }); 35 | return this; 36 | }; 37 | this.subscribe = (subscriber) => { 38 | if (this.subscription) { 39 | throw new Error('only one subscriber for a doc handle'); 40 | } 41 | this.subscription = subscriber; 42 | if (this.state != null && this.clock != null) { 43 | subscriber(this.state, this.clock, this.counter++); 44 | } 45 | return this; 46 | }; 47 | this.subscribeProgress = (subscriber) => { 48 | if (this.progressSubscription) { 49 | throw new Error('only one progress subscriber for a doc handle'); 50 | } 51 | this.progressSubscription = subscriber; 52 | return this; 53 | }; 54 | this.subscribeMessage = (subscriber) => { 55 | if (this.messageSubscription) { 56 | throw new Error('only one document message subscriber for a doc handle'); 57 | } 58 | this.messageSubscription = subscriber; 59 | return this; 60 | }; 61 | this.close = () => { 62 | this.subscription = undefined; 63 | this.messageSubscription = undefined; 64 | this.progressSubscription = undefined; 65 | this.state = null; 66 | this.cleanup(); 67 | }; 68 | this.cleanup = () => { }; 69 | this.changeFn = (_fn) => { }; 70 | this.change = (fn) => { 71 | this.changeFn(fn); 72 | return this; 73 | }; 74 | this.repo = repo; 75 | this.url = url; 76 | } 77 | fork() { 78 | return this.repo.fork(this.url); 79 | } 80 | /* 81 | follow() { 82 | const id = this.repo.create(); 83 | this.repo.follow(id, this.id); 84 | return id; 85 | } 86 | */ 87 | merge(other) { 88 | this.repo.merge(this.url, other.url); 89 | return this; 90 | } 91 | debug() { 92 | this.repo.debug(this.url); 93 | } 94 | } 95 | exports.Handle = Handle; 96 | //# sourceMappingURL=Handle.js.map -------------------------------------------------------------------------------- /dist/CursorStore.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.INFINITY_SEQ = void 0; 7 | const Queue_1 = __importDefault(require("./Queue")); 8 | exports.INFINITY_SEQ = Number.MAX_SAFE_INTEGER; 9 | class CursorStore { 10 | constructor(db) { 11 | this.db = db; 12 | this.updateQ = new Queue_1.default(); 13 | this.preparedGet = this.db.prepare('SELECT * FROM Cursors WHERE repoId = ? AND documentId = ?'); 14 | this.preparedInsert = this.db.prepare(`INSERT INTO Cursors (repoId, documentId, actorId, seq) 15 | VALUES (?, ?, ?, ?) 16 | ON CONFLICT (repoId, documentId, actorId) 17 | DO UPDATE SET seq = excluded.seq WHERE excluded.seq > seq`); 18 | this.preparedEntry = this.db 19 | .prepare('SELECT seq FROM Cursors WHERE repoId = ? AND documentId = ? AND actorId = ?') 20 | .pluck(); 21 | this.preparedDocsWithActor = this.db 22 | .prepare('SELECT documentId FROM Cursors WHERE repoId = ? AND actorId = ? AND seq >= ?') 23 | .pluck(); 24 | this.preparedAllDocumentIds = this.db 25 | .prepare('SELECT DISTINCT documentId from Cursors WHERE repoId = ?') 26 | .pluck(); 27 | } 28 | // NOTE: We return an empty cursor when we don't have a stored cursor. We might want 29 | // to return undefined instead. 30 | get(repoId, docId) { 31 | const rows = this.preparedGet.all(repoId, docId); 32 | return rowsToCursor(rows); 33 | } 34 | update(repoId, docId, cursor) { 35 | const transaction = this.db.transaction((cursorEntries) => { 36 | cursorEntries.forEach(([actorId, seq]) => { 37 | this.preparedInsert.run(repoId, docId, actorId, boundedSeq(seq)); 38 | }); 39 | return this.get(repoId, docId); 40 | }); 41 | const updatedCursor = transaction(Object.entries(cursor)); 42 | const descriptor = [updatedCursor, docId, repoId]; 43 | this.updateQ.push(descriptor); 44 | return descriptor; 45 | } 46 | // NOTE: We return 0 if we don't have a cursor value. This is for backwards compatibility 47 | // with metadata. This might not be the right thing to do. 48 | entry(repoId, docId, actorId) { 49 | return this.preparedEntry.get(repoId, docId, actorId) || 0; 50 | } 51 | // TODO: Should we return cursors and doc ids instead of just doc ids? Look at usage. 52 | docsWithActor(repoId, actorId, seq = 0) { 53 | return this.preparedDocsWithActor.all(repoId, actorId, boundedSeq(seq)); 54 | } 55 | addActor(repoId, docId, actorId, seq = exports.INFINITY_SEQ) { 56 | return this.update(repoId, docId, { [actorId]: boundedSeq(seq) }); 57 | } 58 | getAllDocumentIds(repoId) { 59 | return this.preparedAllDocumentIds.all(repoId); 60 | } 61 | } 62 | exports.default = CursorStore; 63 | function rowsToCursor(rows) { 64 | return rows.reduce((clock, row) => { 65 | clock[row.actorId] = row.seq; 66 | return clock; 67 | }, {}); 68 | } 69 | function boundedSeq(seq) { 70 | return Math.max(0, Math.min(seq, exports.INFINITY_SEQ)); 71 | } 72 | //# sourceMappingURL=CursorStore.js.map -------------------------------------------------------------------------------- /dist/ReplicationManager.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ReplicationManager.js","sourceRoot":"","sources":["../src/ReplicationManager.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/PeerConnection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Debug_1 = __importDefault(require("./Debug")); 7 | const noise_peer_1 = __importDefault(require("noise-peer")); 8 | const Multiplex_1 = __importDefault(require("./Multiplex")); 9 | const MessageBus_1 = __importDefault(require("./MessageBus")); 10 | const pump_1 = __importDefault(require("pump")); 11 | const uuid_1 = __importDefault(require("uuid")); 12 | const StreamLogic_1 = require("./StreamLogic"); 13 | const Heartbeat_1 = __importDefault(require("./Heartbeat")); 14 | const log = Debug_1.default('PeerConnection'); 15 | const VERSION_PREFIX = Buffer.from('hypermerge.v2'); 16 | class PeerConnection { 17 | constructor(rawSocket, info) { 18 | this.onMsg = (msg) => { 19 | this.heartbeat.bump(); 20 | switch (msg.type) { 21 | case 'Id': 22 | this.id = msg.id; 23 | break; 24 | } 25 | }; 26 | this.type = info.type; 27 | this.isClient = info.isClient; 28 | this.heartbeat = new Heartbeat_1.default(2000, { 29 | onBeat: () => this.internalBus.send({ type: 'Heartbeat' }), 30 | onTimeout: () => this.close('timeout'), 31 | }).start(); 32 | this.rawSocket = rawSocket; 33 | this.secureStream = noise_peer_1.default(rawSocket, this.isClient); 34 | this.multiplex = new Multiplex_1.default(); 35 | const prefixMatch = new StreamLogic_1.PrefixMatchPassThrough(VERSION_PREFIX); 36 | this.secureStream.write(VERSION_PREFIX); 37 | pump_1.default(this.secureStream, prefixMatch, this.multiplex, this.secureStream, (err) => { 38 | if (err instanceof StreamLogic_1.InvalidPrefixError) { 39 | this.closeOutdated(err); 40 | } 41 | }); 42 | this.internalBus = this.openBus('PeerConnection', this.onMsg); 43 | if (this.isClient) { 44 | this.id = uuid_1.default(); 45 | this.internalBus.send({ type: 'Id', id: this.id }); 46 | } 47 | } 48 | get isOpen() { 49 | return this.rawSocket.writable; 50 | } 51 | get isClosed() { 52 | return !this.isOpen; 53 | } 54 | openBus(name, subscriber) { 55 | return new MessageBus_1.default(this.openChannel(name), subscriber); 56 | } 57 | openChannel(name) { 58 | if (this.isClosed) 59 | throw new Error('Connection is closed'); 60 | return this.multiplex.openChannel(name); 61 | } 62 | close(reason = 'unknown') { 63 | var _a; 64 | this.log('Closing connection: %s', reason); 65 | this.heartbeat.stop(); 66 | this.rawSocket.destroy(); 67 | (_a = this.onClose) === null || _a === void 0 ? void 0 : _a.call(this, reason); 68 | } 69 | closeOutdated(err) { 70 | const { remoteAddress, remotePort } = this.rawSocket; 71 | const host = `${this.type}@${remoteAddress}:${remotePort}`; 72 | console.log('Closing connection to outdated peer: %s. Prefix: %s', host, err.actual); 73 | return this.close('outdated'); 74 | } 75 | log(str, ...args) { 76 | log(`[${this.id}] ${str}`, ...args); 77 | } 78 | } 79 | exports.default = PeerConnection; 80 | //# sourceMappingURL=PeerConnection.js.map --------------------------------------------------------------------------------