Skip to content

Commit

Permalink
feat: setup WebRTC peer connection management.
Browse files Browse the repository at this point in the history
  • Loading branch information
zicklag committed Dec 30, 2024
1 parent f80e138 commit c531e2b
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 44 deletions.
13 changes: 3 additions & 10 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import App from './app/pages/App';

// import i18n (needs to be bundled ;))
import './app/i18n';
import { PeerjsFrontendManager } from './matrix-shim/peerjsFrontend';

document.body.classList.add(configClass, varsClass);
settings.applyTheme();
Expand All @@ -33,6 +34,8 @@ const mountApp = () => {
root.render(<App />);
};

(globalThis as any).peerManager = new PeerjsFrontendManager();

// Register Service Worker
if ('serviceWorker' in navigator) {
const swUrl =
Expand All @@ -41,16 +44,6 @@ if ('serviceWorker' in navigator) {
: `/dev-sw.js?dev-sw`;

navigator.serviceWorker.register(swUrl, { type: import.meta.env.DEV ? 'module' : 'classic' });
navigator.serviceWorker.addEventListener('message', (event) => {
if (event.data?.type === 'token' && event.data?.responseKey) {
// Get the token for SW.
const token = localStorage.getItem('cinny_access_token') ?? undefined;
event.source!.postMessage({
responseKey: event.data.responseKey,
token,
});
}
});
navigator.serviceWorker.ready.then(mountApp);
} else {
mountApp();
Expand Down
27 changes: 24 additions & 3 deletions src/matrix-shim/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/// <reference lib="WebWorker" />
Expand Down Expand Up @@ -28,10 +29,11 @@ import nacl from 'tweetnacl';

import * as earthstar from '@earthstar/earthstar';
import * as earthstarBrowser from '@earthstar/earthstar/browser';
import { Peer as WebrtcPeer } from 'peerjs';
import { DataConnection } from 'peerjs';
import _ from 'lodash';

import { MatrixDataWrapper } from './data';
import { PeerjsConnectionManager } from './peerjsTransport';

/**
* Resolve a did to it's AtProto handle.
Expand Down Expand Up @@ -106,7 +108,9 @@ export class MatrixShim {

agent?: Agent;

webrtcPeer?: WebrtcPeer;
connectionManager: PeerjsConnectionManager = new PeerjsConnectionManager();

webrtcPeerConns: { [did: string]: DataConnection } = {};

userHandle = '';

Expand Down Expand Up @@ -204,6 +208,20 @@ export class MatrixShim {
return shim;
}

async setPeerIdRecordInPds() {
if (this.agent && this.oauthSession) {
await this.agent.com.atproto.repo.putRecord({
collection: 'peer.pigeon.muni.town',
record: {
$type: 'peer.pigeon.muni.town',
id: this.connectionManager.peerId,
},
repo: this.oauthSession.did,
rkey: 'self',
});
}
}

/**
* Crate the MatrixShim object.
*/
Expand All @@ -223,6 +241,10 @@ export class MatrixShim {
this.kvdb = kvdb;
this.keypair = keypair;

this.connectionManager.openHandlers.push(() => {
this.setPeerIdRecordInPds();
});

const router = AutoRouter();

router.get('/_matrix/custom/authchecktest', () => {
Expand Down Expand Up @@ -561,7 +583,6 @@ export class MatrixShim {
this.agent = new Agent(this.oauthSession);
this.userHandle = (await resolveDid(this.oauthSession.did)) || this.oauthSession.did;
this.kvdb.set('did', this.oauthSession.did);
this.webrtcPeer = new WebrtcPeer(this.oauthSession.did);
}

/**
Expand Down
128 changes: 128 additions & 0 deletions src/matrix-shim/peerjsFrontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import Peer, { DataConnection } from 'peerjs';

export type PeerjsFrontendMessage =
| { type: 'peerOpened'; peerId: string }
| { type: 'peerClosed'; peerId: string }
| { type: 'connOpened'; peerId: string; connectionId: string; transportId: string }
| { type: 'connData'; peerId: string; connectionId: string; data: unknown }
| { type: 'connClosed'; peerId: string; connectionId: string }
| { type: 'incomingConnected'; peerId: string; connId: string; remotePeerId: string };

export type PeerjsBackendMessage =
| { type: 'getPeerId' }
| {
type: 'connect';
transportId: string;
remotePeerId: string;
}
| { type: 'sendData'; peerId: string; connectionId: string; data: unknown }
| { type: 'closeConn'; peerId: string; connectionId: string };

export class PeerjsFrontendManager {
peer: Peer;

connections: { [id: string]: DataConnection } = {};

sender: BroadcastChannel = new BroadcastChannel('matrix-shim-peerjs-frontend');

receiver: BroadcastChannel = new BroadcastChannel('matrix-shim-peerjs-backend');

constructor() {
this.peer = new Peer();
this.peer.on('open', (id) => {
console.trace('Peer opened:', id);
const message: PeerjsFrontendMessage = { type: 'peerOpened', peerId: id };
this.sender.postMessage(message);
});

// When we receive a connection from outside
this.peer.on('connection', (conn) => {
// Add the connection to the list
this.connections[conn.connectionId] = conn;
// And send a connected event to the service worker
const m: PeerjsFrontendMessage = {
type: 'incomingConnected',
peerId: this.peer.id,
connId: conn.connectionId,
remotePeerId: conn.peer,
};
this.sender.postMessage(m);
});

// When the peer closes
this.peer.on('close', () => {
// Tell the service worker
const m: PeerjsFrontendMessage = {
type: 'peerClosed',
peerId: this.peer.id,
};
this.sender.postMessage(m);
});

// When we receive a message from our service worker
this.sender.addEventListener('message', (event) => {
const message: PeerjsBackendMessage = event.data;

// If we should connect to another peer
if (message.type === 'connect') {
// TODO: support multiple browser tabs being open.
//
// Right now all open tabs will try to connect when they receive this message which is not
// good.

// Create the connection
const conn = this.peer.connect(message.remotePeerId, { reliable: true });

// When the connection opens
conn.on('open', () => {
// Tell the service worker the connection has opened.
const m: PeerjsFrontendMessage = {
type: 'connOpened',
peerId: this.peer.id,
connectionId: conn.connectionId,
transportId: message.transportId,
};
this.sender.postMessage(m);
});

// When the connection has data
conn.on('data', (data) => {
// Tell the service worker the connection has opened.
const m: PeerjsFrontendMessage = {
type: 'connData',
peerId: this.peer.id,
connectionId: conn.connectionId,
data,
};
this.sender.postMessage(m);
});

// When the connection closees
conn.on('close', () => {
// Tell the service worker the connection has opened.
const m: PeerjsFrontendMessage = {
type: 'connClosed',
peerId: this.peer.id,
connectionId: conn.connectionId,
};
this.sender.postMessage(m);
});

// If the service worker wants us to send data
} else if (message.type === 'sendData' && message.peerId === this.peer.id) {
// Get the connection and send it
const conn = this.connections[message.connectionId];
if (conn) {
conn.send(message.data);
}
} else if (message.type === 'getPeerId') {
// Tell the service worker the connection has opened.
const m: PeerjsFrontendMessage = {
type: 'peerOpened',
peerId: this.peer.id,
};
this.sender.postMessage(m);
}
});
}
}
Loading

0 comments on commit c531e2b

Please sign in to comment.