Skip to content

Commit

Permalink
🌳 copy over from monorepo (#1)
Browse files Browse the repository at this point in the history
* refactor types

* add ref: eventemitter3

* remove rxjs (reimplemented with EventEmitter)

* Create ci.node.yml

* placeholder test

* add CI bade to README

* Create CHANGELOG.md
  • Loading branch information
philcockfield committed Feb 26, 2024
1 parent ecad9b9 commit 0f546a8
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 19 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/ci.node.yml
@@ -0,0 +1,30 @@
name: ci.node

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:
name: node.ci
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"

- run: yarn install
- run: yarn audit
- run: yarn build
- run: yarn test
28 changes: 28 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,28 @@
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] - YYYY-MM-DD
#### Added
#### Changed
#### Deprecated
#### Removed
#### Fixed
#### Security



## [0.0.1] - YYYY-MM-DD
#### Added
- Setup initial project structure.
- Copied over from [source](https://github.com/cellplatform/platform-0.2.0/tree/main/code/ext/ext.lib.automerge.webrtc/src/Store.Network.WebrtcAdapter) monorepo.
- Setup CI/CD.
#### Changed
#### Deprecated
#### Removed
#### Fixed
#### Security




1 change: 1 addition & 0 deletions README.md
@@ -1,3 +1,4 @@
[![ci.node](https://github.com/philcockfield/automerge-repo-network-peerjs/actions/workflows/ci.node.yml/badge.svg)](https://github.com/philcockfield/automerge-repo-network-peerjs/actions/workflows/ci.node.yml)
# automerge-repo-network-peerjs

A network adapter for [automerge-repo](https://github.com/automerge/automerge-repo) for WebRTC (P2P), based on [MessageChannelNetworkAdapter](https://github.com/automerge/automerge-repo/blob/main/packages/automerge-repo-network-messagechannel/src/index.ts) (point-to-point).
Expand Down
8 changes: 5 additions & 3 deletions package.json
Expand Up @@ -11,11 +11,13 @@
"test": "vitest"
},
"dependencies": {
"@automerge/automerge-repo": "1.1.1"
"@automerge/automerge-repo": "1.1.1",
"eventemitter3": "5.0.1",
"peerjs": "1.5.2"
},
"devDependencies": {
"typescript": "^5.3.3",
"vitest": "^1.3.1"
"typescript": "5.3.3",
"vitest": "1.3.1"
},
"publishConfig": {
"access": "public"
Expand Down
7 changes: 7 additions & 0 deletions src/NetworkAdapter.test.ts
@@ -0,0 +1,7 @@
import { describe, expect, it } from "vitest";

describe("NetworkAdapter", () => {
it("placeholder", () => {
expect(123).to.eql(123);
});
});
115 changes: 115 additions & 0 deletions src/NetworkAdapter.ts
@@ -0,0 +1,115 @@
import { NetworkAdapter } from "@automerge/automerge-repo";
import { EventEmitter } from "eventemitter3";
import type * as t from "./t.js";

/**
* An Automerge repo network-adapter for WebRTC (P2P)
*
* Based on:
* MessageChannelNetworkAdapter (point-to-point)
* https://github.com/automerge/automerge-repo/blob/main/packages/automerge-repo-network-messagechannel/src/index.ts
*
*/
export class WebrtcNetworkAdapter extends NetworkAdapter {
#conn: t.DataConnection;
#isReady = false;
#disconnected = new EventEmitter<"disconnected">();

constructor(conn: t.DataConnection) {
if (!conn) throw new Error(`A peerjs data-connection is required`);
super();
this.#conn = conn;
}

connect(peerId: t.PeerId) {
const senderId = (this.peerId = peerId);
const conn = this.#conn;

const handleOpen = () => this.#transmit({ type: "arrive", senderId, peerMetadata: {} });
const handleClose = () => this.emit("close");
const handleData = (e: any) => {
const msg = e as t.WebrtcMessage;

/**
* Arrive.
*/
if (msg.type === "arrive") {
const { peerMetadata } = msg as t.ArriveMessage;
const targetId = msg.senderId;
this.#transmit({ type: "welcome", senderId, targetId, peerMetadata });
this.#announceConnection(targetId, peerMetadata);
return;
}

/**
* Welcome.
*/
if (msg.type === "welcome") {
const { peerMetadata } = msg as t.WelcomeMessage;
this.#announceConnection(msg.senderId, peerMetadata);
return;
}

/**
* Default (data payload).
*/
let payload = msg as t.Message;
if ("data" in msg) payload = { ...payload, data: toUint8Array(msg.data!) };
this.emit("message", payload);
};

conn.on("open", handleOpen);
conn.on("close", handleClose);
conn.on("data", handleData);

this.#disconnected.on("disconnected", () => {
this.#isReady = false;
conn.off("open", handleOpen);
conn.off("close", handleClose);
conn.off("data", handleData);
});

/**
* Mark this channel as ready after 100ms, at this point there
* must be something weird going on at the other end to cause us
* to receive no response.
*/
setTimeout(() => this.#setAsReady(), 100);
}

disconnect() {
this.#disconnected.emit("disconnected");
}

send(message: t.RepoMessage) {
if (!this.#conn) throw new Error("Connection not ready");
if ("data" in message) {
this.#transmit({ ...message, data: toUint8Array(message.data) });
} else {
this.#transmit(message);
}
}

#transmit(message: t.WebrtcMessage) {
if (!this.#conn) throw new Error("Connection not ready");
this.#conn.send(message);
}

#setAsReady() {
if (this.#isReady) return;
this.#isReady = true;
this.emit("ready", { network: this });
}

#announceConnection(peerId: t.PeerId, peerMetadata: t.PeerMetadata) {
this.#setAsReady();
this.emit("peer-candidate", { peerId, peerMetadata });
}
}

/**
* Helpers
*/
function toUint8Array(input: Uint8Array): Uint8Array {
return input instanceof Uint8Array ? input : new Uint8Array(input);
}
7 changes: 0 additions & 7 deletions src/index.test.ts

This file was deleted.

6 changes: 1 addition & 5 deletions src/index.ts
@@ -1,5 +1 @@
console.log("index.ts");

export function sum(a: number, b: number) {
return a + b;
}
export { WebrtcNetworkAdapter } from "./NetworkAdapter.js";
66 changes: 66 additions & 0 deletions src/t.ts
@@ -0,0 +1,66 @@
/**
* @automerge
*/
import type { Message, PeerId, RepoMessage, StorageId } from "@automerge/automerge-repo";
export { Message, PeerId, RepoMessage };

/**
* @peerjs
*/
export type { DataConnection } from "peerjs";

/**
* @internal
* Based on:
* MessageChannelNetworkAdapter
* https://github.com/automerge/automerge-repo/blob/main/packages/automerge-repo-network-messagechannel/src/index.ts
*/
export type IODirection = "incoming" | "outgoing";

export type WebrtcMessage = ArriveMessage | WelcomeMessage | Message;
export type WebrtcMessageAlert = {
direction: IODirection;
message: WebrtcMessage;
};

/**
* Describes a peer intent to the system
* storageId: the key for syncState to decide what the other peer already has
* isEphemeral: to decide if we bother recording this peer's sync state
*/
export interface PeerMetadata {
storageId?: StorageId;
isEphemeral?: boolean;
}

/**
* Notify the network that we have arrived so everyone knows our peer ID
*/
export type ArriveMessage = {
type: "arrive";

/** The peer ID of the sender of this message */
senderId: PeerId;

/** Arrive messages don't have a targetId */
targetId?: never;

/** The peer metadata of the sender of this message */
peerMetadata: PeerMetadata;
};

/**
* Respond to an arriving peer with our peer ID
*/
export type WelcomeMessage = {
type: "welcome";

/** The peer ID of the recipient sender this message */
senderId: PeerId;

/** The peer ID of the recipient of this message */
targetId: PeerId;

/** The peer metadata of the sender of this message */
peerMetadata: PeerMetadata;
};

0 comments on commit 0f546a8

Please sign in to comment.