Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/interfaces/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export * from "./constants.js";
export * from "./sharding.js";
export * from "./health_status.js";
export * from "./discovery.js";
export * from "./webrtc.js";
3 changes: 3 additions & 0 deletions packages/interfaces/src/waku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { Protocols } from "./protocols.js";
import type { IRelay } from "./relay.js";
import type { ShardId } from "./sharding.js";
import type { IStore } from "./store.js";
import type { IWebRTC } from "./webrtc.js";

export type CreateDecoderParams = {
contentTopic: string;
Expand Down Expand Up @@ -62,6 +63,7 @@ export interface IWaku {
store?: IStore;
filter?: IFilter;
lightPush?: ILightPush;
webRTC?: IWebRTC;

/**
* Emits events related to the Waku node.
Expand Down Expand Up @@ -272,6 +274,7 @@ export interface LightNode extends IWaku {
store: IStore;
filter: IFilter;
lightPush: ILightPush;
webRTC: IWebRTC;
}

export interface RelayNode extends IWaku {
Expand Down
93 changes: 93 additions & 0 deletions packages/interfaces/src/webrtc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { PeerId, TypedEventEmitter } from "@libp2p/interface";

export enum WebRTCEvent {
InboundRequest = "webRTC:inbound-request",
Connected = "webRTC:connected",
Closed = "webRTC:closed",
Rejected = "webRTC:rejected"
}

export interface IWebRTCEvents {
/**
* Used to listen to incoming WebRTC connection request.
*
* @example
* ```typescript
* waku.addEventListener(WebRTCEvent.Inbound, (event) => {
* const requesterPeerId = event.detail;
*
* if (requesterPeerId.equals(expectedPeerId)) {
* waku.webRTC.accept(requesterPeerId);
* } else {
* waku.webRTC.hangUp(requesterPeerId);
* }
* });
*/
[WebRTCEvent.InboundRequest]: CustomEvent<PeerId>;

/**
* Used to listen to get notified when a WebRTC connection is established.
*
* @example
* ```typescript
* waku.addEventListener(WebRTCEvent.Connected, (event) => {
* const connection = event.detail; // RTCPeerConnection
* });
* ```
*/
[WebRTCEvent.Connected]: CustomEvent<RTCPeerConnection>;
}

export type PeerIdOrString = PeerId | string;

export type WebRTCDialOptions = {
peerId: PeerIdOrString;
timeoutMs?: number;
};

export interface IWebRTC {
/**
* Used to listen to incoming WebRTC connection request or progress of established connections.
*/
events: TypedEventEmitter<IWebRTCEvents>;

/**
* Starts the listening to incoming WebRTC connection requests.
*/
start(): Promise<void>;

/**
* Stops the listening to incoming WebRTC connection requests.
*/
stop(): Promise<void>;

/**
* Dials a peer using Waku WebRTC protocol.
*/
dial(options: WebRTCDialOptions): Promise<void>;

/**
* Accepts a WebRTC connection request from a peer.
*/
accept(peerId: PeerIdOrString): void;

/**
* Hang up a WebRTC connection to a peer or incoming connection request.
*/
hangUp(peerId: PeerIdOrString): void;

/**
* Checks if a WebRTC connection is established to a peer.
*/
isConnected(peerId: PeerIdOrString): boolean;

/**
* Gets the list of connected peers using Waku WebRTC protocol.
*/
getConnectedPeers(): PeerId[];

/**
* Gets map of WebRTC connections by peer ID.
*/
getConnections(): Record<string, RTCPeerConnection>;
}
18 changes: 18 additions & 0 deletions packages/sdk/src/waku/waku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
IStore,
IWaku,
IWakuEventEmitter,
IWebRTC,
Libp2p,
NetworkConfig
} from "@waku/interfaces";
Expand All @@ -35,6 +36,7 @@ import { HealthIndicator } from "../health_indicator/index.js";
import { LightPush } from "../light_push/index.js";
import { PeerManager } from "../peer_manager/index.js";
import { Store } from "../store/index.js";
import { WebRTC } from "../webrtc/index.js";

import { waitForRemotePeer } from "./wait_for_remote_peer.js";

Expand All @@ -52,6 +54,7 @@ export class WakuNode implements IWaku {
public store?: IStore;
public filter?: IFilter;
public lightPush?: ILightPush;
public webRTC?: IWebRTC;

public readonly events: IWakuEventEmitter = new TypedEventEmitter();

Expand Down Expand Up @@ -126,6 +129,21 @@ export class WakuNode implements IWaku {
});
}

if (this.lightPush && this.filter) {
const webRtcContentTopic = WebRTC.buildContentTopic(this.libp2p.peerId);

this.webRTC = new WebRTC({
lightPush: this.lightPush,
filter: this.filter,
encoder: this.createEncoder({
contentTopic: webRtcContentTopic
}),
decoder: this.createDecoder({
contentTopic: webRtcContentTopic
})
});
}

log.info(
"Waku node created",
peerId,
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/webrtc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { WebRTC } from "./webrtc.js";
121 changes: 121 additions & 0 deletions packages/sdk/src/webrtc/webrtc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { PeerId, TypedEventEmitter } from "@libp2p/interface";
import type {
IDecodedMessage,
IDecoder,
IEncoder,
IFilter,
ILightPush,
IWebRTC,
IWebRTCEvents,
PeerIdOrString,
WebRTCDialOptions
} from "@waku/interfaces";

type WebRTCConstructorOptions = {
lightPush: ILightPush;
filter: IFilter;
decoder: IDecoder<IDecodedMessage>;
encoder: IEncoder;
};

export class WebRTC implements IWebRTC {
private readonly lightPush: ILightPush;
private readonly filter: IFilter;

private readonly decoder: IDecoder<IDecodedMessage>;
private readonly encoder: IEncoder;

public readonly events: TypedEventEmitter<IWebRTCEvents> =
new TypedEventEmitter();

private isStarted = false;

public static buildContentTopic(peerId: PeerId): string {
return `/js-waku-webrtc/1/${peerId.toString()}/proto`;
}

public constructor(options: WebRTCConstructorOptions) {
this.lightPush = options.lightPush;
this.filter = options.filter;

this.decoder = options.decoder;
this.encoder = options.encoder;

this.handleInboundRequest = this.handleInboundRequest.bind(this);
}

public async start(): Promise<void> {
if (this.isStarted) {
return;
}

this.isStarted = true;
await this.subscribeToInboundRequests();
}

public async stop(): Promise<void> {
if (!this.isStarted) {
return;
}

this.isStarted = false;
await this.unsubscribeFromInboundRequests();
}

public async dial(options: WebRTCDialOptions): Promise<void> {
// TODO: implement
}

public accept(peerId: PeerIdOrString): void {
// TODO: implement
}

public hangUp(peerId: PeerIdOrString): void {
// TODO: implement
}

public isConnected(peerId: PeerIdOrString): boolean {
// TODO: implement
return false;
}

public getConnectedPeers(): PeerId[] {
// TODO: implement
return [];
}

public getConnections(): Record<string, RTCPeerConnection> {
// TODO: implement
return {};
}

private async subscribeToInboundRequests(): Promise<void> {
await this.filter.subscribe(this.decoder, this.handleInboundRequest);
}

private async unsubscribeFromInboundRequests(): Promise<void> {
await this.filter.unsubscribe(this.decoder);
}

private handleInboundRequest(message: IDecodedMessage): void {
/*
const decryptedMessage = decrypt(message.payload, this.privateKey);
switch (decryptedMessage.type) {
case "dial":
break;
case "ack":
break;
case "answer":
break;
case "reject":
break;
case "candidate":
break;
case "close":
break;
default:
break;
}
*/
}
}
Loading