Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@automerge/automerge-repo
Advanced tools
A repository object to manage a collection of automerge documents
This is a wrapper for the Automerge CRDT library which provides facilities to support working with many documents at once, as well as pluggable networking and storage.
This is the core library. It handles dispatch of events and provides shared functionality such as deciding which peers to connect to or when to write data out to storage.
Other packages in this monorepo include:
This library provides two main components: the Repo
itself, and the DocHandle
s it contains.
A Repo
exposes these methods:
create<T>()
Automerge.Doc
and returns a DocHandle
for it.find<T>(docId: DocumentId)
delete(docId: DocumentId)
.on("document", ({handle: DocHandle}) => void)
.on("delete-document", ({handle: DocHandle}) => void)
A DocHandle
is a wrapper around an Automerge.Doc
. Its primary function is to dispatch changes to
the document.
handle.change((doc: T) => void)
handle.value()
Promise<Doc<T>>
that will contain the current value of the document.
it waits until the document has finished loading and/or synchronizing over the network before
returning a value.When required, you can also access the underlying document directly, but only after the handle is ready:
if (handle.ready()) {
doc = handle.doc
} else {
handle.value().then(d => {
doc = d
})
}
A DocHandle
also emits these events:
change({handle: DocHandle, doc: Doc<T>})
value()
from the
handle.patch({handle: DocHandle, patches: Patch[], patchInfo: PatchInfo})
Useful for manual increment maintenance of a video, most notably for text editors.delete
The repo needs to be configured with storage and network adapters. If you give it neither, it will still work, but you won't be able to find any data and data created won't outlast the process.
Multiple network adapters (even of the same type) can be added to a repo, even after it is created.
A repo currently only supports a single storage adapter, and it must be provided at creation.
Here is an example of creating a repo with a indexeddb storage adapter and a broadcast channel network adapter:
const repo = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
storage: new IndexedDBStorageAdapter(),
sharePolicy: async (peerId: PeerId, documentId: DocumentId) => true // this is the default
})
The share policy is used to determine which document in your repo should be automatically shared with other peers. The default setting is to share all documents with all peers.
Warning If your local repo has deleted a document, a connecting peer with the default share policy will still share that document with you.
You can override this by providing a custom share policy. The function should return a promise resolving to a boolean value indicating whether the document should be shared with the peer.
The share policy will not stop a document being requested by another peer by its DocumentId
.
## Starting the demo app
```bash
yarn
yarn dev
The following instructions will get you a working React app running in a browser.
yarn create vite
# Project name: hello-automerge-repo
# Select a framework: React
# Select a variant: TypeScript
cd hello-automerge-repo
yarn
yarn add @automerge/automerge @automerge/automerge-repo-react-hooks @automerge/automerge-repo-network-broadcastchannel @automerge/automerge-repo-storage-indexeddb vite-plugin-wasm vite-plugin-top-level-await
Edit the vite.config.ts
. (This is all need to work around packaging hiccups due to WASM. We look
forward to the day that we can delete this step entirely.)
// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import wasm from "vite-plugin-wasm"
import topLevelAwait from "vite-plugin-top-level-await"
export default defineConfig({
plugins: [wasm(), topLevelAwait(), react()],
worker: {
format: "es",
plugins: [wasm(), topLevelAwait()],
},
optimizeDeps: {
// This is necessary because otherwise `vite dev` includes two separate
// versions of the JS wrapper. This causes problems because the JS
// wrapper has a module level variable to track JS side heap
// allocations, and initializing this twice causes horrible breakage
exclude: [
"@automerge/automerge-wasm",
"@automerge/automerge-wasm/bundler/bindgen_bg.wasm",
"@syntect/wasm",
],
},
server: {
fs: {
strict: false,
},
},
})
Now set up the repo in src/main.tsx
by importing the bits, creating the repo, and passing down a
RepoContext. We also create a document and store its documentId
in localStorage.
// src/main.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App.js"
import { Repo } from "@automerge/automerge-repo"
import { BroadcastChannelNetworkAdapter } from "@automerge/automerge-repo-network-broadcastchannel"
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb"
import { RepoContext } from "@automerge/automerge-repo-react-hooks"
const repo = new Repo({
network: [new BroadcastChannelNetworkAdapter()],
storage: new IndexedDBStorageAdapter(),
})
let rootDocId = localStorage.rootDocId
if (!rootDocId) {
const handle = repo.create()
localStorage.rootDocId = rootDocId = handle.documentId
}
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<RepoContext.Provider value={repo}>
<React.StrictMode>
<App documentId={rootDocId} />
</React.StrictMode>
</RepoContext.Provider>
)
Now update App.tsx
to load the document from the Repo based on the documentId passed in. Then, use
the document to render a button that increments the count.
// App.tsx
import { useDocument } from "@automerge/automerge-repo-react-hooks"
import { DocumentId } from "@automerge/automerge-repo"
interface Doc {
count: number
}
export default function App(props: { documentId: DocumentId }) {
const [doc, changeDoc] = useDocument<Doc>(props.documentId)
return (
<button
onClick={() => {
changeDoc((d: any) => {
d.count = (d.count || 0) + 1
})
}}
>
count is: {doc?.count ?? 0}
</button>
)
}
You should now have a working React application using Automerge. Try running it with yarn dev
, and
open it in two browser windows. You should see the count increment in both windows.
This application is also available as a package in this repo in
automerge-repo-demo-counter. You can run it with yarn dev:demo
.
First, get a sync-server running locally, following the instructions for the automerge-repo-sync-server package.
Next, update your application to synchronize with it:
Install the websocket network adapter:
yarn add automerge-repo-network-websocket
Now import it and add it to your list of network adapters:
// main.tsx
import { BrowserWebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket" // <-- add this line
// ...
const repo = new Repo({
network: [
new BroadcastChannelNetworkAdapter(),
new BrowserWebSocketClientAdapter("ws://localhost:3030"), // <-- add this line
],
storage: new IndexedDBStorageAdapter(),
})
// ...
And you're finished! You can test that your sync server is opening the same document in two
different browsers (e.g. Chrome and Firefox). (Note that with our current trivial implementation
you'll need to manually copy the rootDocId
value between the browsers.)
Originally authored by Peter van Hardenberg.
With gratitude for contributions by:
FAQs
A repository object to manage a collection of automerge documents
We found that @automerge/automerge-repo demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.