Skip to content

Commit 0f546a8

Browse files
🌳 copy over from monorepo (#1)
* refactor types * add ref: eventemitter3 * remove rxjs (reimplemented with EventEmitter) * Create ci.node.yml * placeholder test * add CI bade to README * Create CHANGELOG.md
1 parent ecad9b9 commit 0f546a8

File tree

10 files changed

+302
-19
lines changed

10 files changed

+302
-19
lines changed

‎.github/workflows/ci.node.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: ci.node
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
jobs:
10+
build:
11+
name: node.ci
12+
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
node-version: [20.x]
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v3
20+
21+
- name: Use Node.js ${{ matrix.node-version }}
22+
uses: actions/setup-node@v3
23+
with:
24+
node-version: ${{ matrix.node-version }}
25+
cache: "yarn"
26+
27+
- run: yarn install
28+
- run: yarn audit
29+
- run: yarn build
30+
- run: yarn test

‎CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Change Log
2+
All notable changes to this project will be documented in this file.
3+
This project adheres to [Semantic Versioning](http://semver.org/).
4+
5+
## [Unreleased] - YYYY-MM-DD
6+
#### Added
7+
#### Changed
8+
#### Deprecated
9+
#### Removed
10+
#### Fixed
11+
#### Security
12+
13+
14+
15+
## [0.0.1] - YYYY-MM-DD
16+
#### Added
17+
- Setup initial project structure.
18+
- 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.
19+
- Setup CI/CD.
20+
#### Changed
21+
#### Deprecated
22+
#### Removed
23+
#### Fixed
24+
#### Security
25+
26+
27+
28+

‎README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
[![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)
12
# automerge-repo-network-peerjs
23

34
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).

‎package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
"test": "vitest"
1212
},
1313
"dependencies": {
14-
"@automerge/automerge-repo": "1.1.1"
14+
"@automerge/automerge-repo": "1.1.1",
15+
"eventemitter3": "5.0.1",
16+
"peerjs": "1.5.2"
1517
},
1618
"devDependencies": {
17-
"typescript": "^5.3.3",
18-
"vitest": "^1.3.1"
19+
"typescript": "5.3.3",
20+
"vitest": "1.3.1"
1921
},
2022
"publishConfig": {
2123
"access": "public"

‎src/NetworkAdapter.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
describe("NetworkAdapter", () => {
4+
it("placeholder", () => {
5+
expect(123).to.eql(123);
6+
});
7+
});

‎src/NetworkAdapter.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { NetworkAdapter } from "@automerge/automerge-repo";
2+
import { EventEmitter } from "eventemitter3";
3+
import type * as t from "./t.js";
4+
5+
/**
6+
* An Automerge repo network-adapter for WebRTC (P2P)
7+
*
8+
* Based on:
9+
* MessageChannelNetworkAdapter (point-to-point)
10+
* https://github.com/automerge/automerge-repo/blob/main/packages/automerge-repo-network-messagechannel/src/index.ts
11+
*
12+
*/
13+
export class WebrtcNetworkAdapter extends NetworkAdapter {
14+
#conn: t.DataConnection;
15+
#isReady = false;
16+
#disconnected = new EventEmitter<"disconnected">();
17+
18+
constructor(conn: t.DataConnection) {
19+
if (!conn) throw new Error(`A peerjs data-connection is required`);
20+
super();
21+
this.#conn = conn;
22+
}
23+
24+
connect(peerId: t.PeerId) {
25+
const senderId = (this.peerId = peerId);
26+
const conn = this.#conn;
27+
28+
const handleOpen = () => this.#transmit({ type: "arrive", senderId, peerMetadata: {} });
29+
const handleClose = () => this.emit("close");
30+
const handleData = (e: any) => {
31+
const msg = e as t.WebrtcMessage;
32+
33+
/**
34+
* Arrive.
35+
*/
36+
if (msg.type === "arrive") {
37+
const { peerMetadata } = msg as t.ArriveMessage;
38+
const targetId = msg.senderId;
39+
this.#transmit({ type: "welcome", senderId, targetId, peerMetadata });
40+
this.#announceConnection(targetId, peerMetadata);
41+
return;
42+
}
43+
44+
/**
45+
* Welcome.
46+
*/
47+
if (msg.type === "welcome") {
48+
const { peerMetadata } = msg as t.WelcomeMessage;
49+
this.#announceConnection(msg.senderId, peerMetadata);
50+
return;
51+
}
52+
53+
/**
54+
* Default (data payload).
55+
*/
56+
let payload = msg as t.Message;
57+
if ("data" in msg) payload = { ...payload, data: toUint8Array(msg.data!) };
58+
this.emit("message", payload);
59+
};
60+
61+
conn.on("open", handleOpen);
62+
conn.on("close", handleClose);
63+
conn.on("data", handleData);
64+
65+
this.#disconnected.on("disconnected", () => {
66+
this.#isReady = false;
67+
conn.off("open", handleOpen);
68+
conn.off("close", handleClose);
69+
conn.off("data", handleData);
70+
});
71+
72+
/**
73+
* Mark this channel as ready after 100ms, at this point there
74+
* must be something weird going on at the other end to cause us
75+
* to receive no response.
76+
*/
77+
setTimeout(() => this.#setAsReady(), 100);
78+
}
79+
80+
disconnect() {
81+
this.#disconnected.emit("disconnected");
82+
}
83+
84+
send(message: t.RepoMessage) {
85+
if (!this.#conn) throw new Error("Connection not ready");
86+
if ("data" in message) {
87+
this.#transmit({ ...message, data: toUint8Array(message.data) });
88+
} else {
89+
this.#transmit(message);
90+
}
91+
}
92+
93+
#transmit(message: t.WebrtcMessage) {
94+
if (!this.#conn) throw new Error("Connection not ready");
95+
this.#conn.send(message);
96+
}
97+
98+
#setAsReady() {
99+
if (this.#isReady) return;
100+
this.#isReady = true;
101+
this.emit("ready", { network: this });
102+
}
103+
104+
#announceConnection(peerId: t.PeerId, peerMetadata: t.PeerMetadata) {
105+
this.#setAsReady();
106+
this.emit("peer-candidate", { peerId, peerMetadata });
107+
}
108+
}
109+
110+
/**
111+
* Helpers
112+
*/
113+
function toUint8Array(input: Uint8Array): Uint8Array {
114+
return input instanceof Uint8Array ? input : new Uint8Array(input);
115+
}

‎src/index.test.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

‎src/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
console.log("index.ts");
2-
3-
export function sum(a: number, b: number) {
4-
return a + b;
5-
}
1+
export { WebrtcNetworkAdapter } from "./NetworkAdapter.js";

‎src/t.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* @automerge
3+
*/
4+
import type { Message, PeerId, RepoMessage, StorageId } from "@automerge/automerge-repo";
5+
export { Message, PeerId, RepoMessage };
6+
7+
/**
8+
* @peerjs
9+
*/
10+
export type { DataConnection } from "peerjs";
11+
12+
/**
13+
* @internal
14+
* Based on:
15+
* MessageChannelNetworkAdapter
16+
* https://github.com/automerge/automerge-repo/blob/main/packages/automerge-repo-network-messagechannel/src/index.ts
17+
*/
18+
export type IODirection = "incoming" | "outgoing";
19+
20+
export type WebrtcMessage = ArriveMessage | WelcomeMessage | Message;
21+
export type WebrtcMessageAlert = {
22+
direction: IODirection;
23+
message: WebrtcMessage;
24+
};
25+
26+
/**
27+
* Describes a peer intent to the system
28+
* storageId: the key for syncState to decide what the other peer already has
29+
* isEphemeral: to decide if we bother recording this peer's sync state
30+
*/
31+
export interface PeerMetadata {
32+
storageId?: StorageId;
33+
isEphemeral?: boolean;
34+
}
35+
36+
/**
37+
* Notify the network that we have arrived so everyone knows our peer ID
38+
*/
39+
export type ArriveMessage = {
40+
type: "arrive";
41+
42+
/** The peer ID of the sender of this message */
43+
senderId: PeerId;
44+
45+
/** Arrive messages don't have a targetId */
46+
targetId?: never;
47+
48+
/** The peer metadata of the sender of this message */
49+
peerMetadata: PeerMetadata;
50+
};
51+
52+
/**
53+
* Respond to an arriving peer with our peer ID
54+
*/
55+
export type WelcomeMessage = {
56+
type: "welcome";
57+
58+
/** The peer ID of the recipient sender this message */
59+
senderId: PeerId;
60+
61+
/** The peer ID of the recipient of this message */
62+
targetId: PeerId;
63+
64+
/** The peer metadata of the sender of this message */
65+
peerMetadata: PeerMetadata;
66+
};

0 commit comments

Comments
 (0)