From df4dc22e0294ccebfb2401db685de0970c9ecaed Mon Sep 17 00:00:00 2001 From: Chinmay Kousik Date: Thu, 28 Jul 2022 21:21:44 +0530 Subject: [PATCH 1/5] skeleton structs --- .gitignore | 42 +++++++++++++++++++++++++++ .vscode/launch.json | 15 ++++++++++ LICENSE | 4 +++ LICENSE-APACHE | 5 ++++ LICENSE-MIT | 19 ++++++++++++ README.md | 11 +++++++ package.json | 27 +++++++++++++++++ src/connection.ts | 66 ++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 2 ++ src/options.ts | 11 +++++++ src/sdp.ts | 65 +++++++++++++++++++++++++++++++++++++++++ src/socket.ts | 54 ++++++++++++++++++++++++++++++++++ src/stream.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++ src/transport.ts | 46 +++++++++++++++++++++++++++++ test/node.ts | 6 ++++ tsconfig.json | 13 +++++++++ 16 files changed, 456 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 LICENSE create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 package.json create mode 100644 src/connection.ts create mode 100644 src/index.ts create mode 100644 src/options.ts create mode 100644 src/sdp.ts create mode 100644 src/socket.ts create mode 100644 src/stream.ts create mode 100644 src/transport.ts create mode 100644 test/node.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b74316f --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.swp + +pids +logs +results +tmp + +# Build +public/css/main.css + +# Coverage reports +coverage + +# API keys and secrets +.env + +# Dependency directory +node_modules +bower_components + +# Editors +.idea +*.iml + +# OS metadata +.DS_Store +Thumbs.db + +# Ignore built ts files +dist/**/* + +# ignore yarn.lock +yarn.lock +package-lock.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7a9dfa0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20ce483 --- /dev/null +++ b/LICENSE @@ -0,0 +1,4 @@ +This project is dual licensed under MIT and Apache-2.0. + +MIT: https://www.opensource.org/licenses/mit +Apache-2.0: https://www.apache.org/licenses/license-2.0 diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..14478a3 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,5 @@ +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..72dc60d --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3450546 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# @libp2p/webrtc + +## Install + +## Usage + +## API + +## Contribute + +## License diff --git a/package.json b/package.json new file mode 100644 index 0000000..782edf4 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "js-libp2p-webrtc", + "version": "1.0.0", + "description": "Dial peer using webrtc", + "main": "index.js", + "author": "", + "license": "Apache-2.0 or MIT", + "type": "module", + "scripts": { + "build": "aegir build", + "test": "aegir test" + }, + "devDependencies": { + "@types/uuid": "^8.3.4", + "aegir": "^37.4.6" + }, + "dependencies": { + "@libp2p/components": "^2.0.1", + "@libp2p/interface-transport": "^1.0.2", + "@libp2p/interfaces": "^3.0.3", + "@libp2p/logger": "^2.0.0", + "abortable-iterator": "^4.0.2", + "socket.io-client": "^4.1.2", + "typescript": "^4.7.4", + "uuid": "^8.3.2" + } +} diff --git a/src/connection.ts b/src/connection.ts new file mode 100644 index 0000000..5da11a2 --- /dev/null +++ b/src/connection.ts @@ -0,0 +1,66 @@ +import { Connection } from '@libp2p/interface-connection' +import {ConnectionStat}from '@libp2p/interface-connection' +import { Stream, Direction } from '@libp2p/interface-connection' +import { PeerId } from '@libp2p/interface-peer-id'; +import { AbortOptions }from '@libp2p/interfaces'; +import { logger } from '@libp2p/logger' +import { Multiaddr } from '@multiformats/multiaddr'; +import {v4 as genUuid} from 'uuid'; + +const log = logger('libp2p:webrtc:connection') + +type ConnectionInit = { + id: string + localPeer: PeerId + localAddr?: Multiaddr + remotePeer: PeerId + remoteAddr: Multiaddr + direction: Direction + tags?: string[] + stat: ConnectionStat + pc: RTCPeerConnection + credential_string: string +} + +export class WebRTCConnection implements Connection { + id: string; + stat: ConnectionStat; + remoteAddr: Multiaddr; + remotePeer: PeerId; + tags: string[] = []; + streams: Stream[] = []; + direction: Direction; + + + constructor(init: ConnectionInit) { + this.streams = [] + this.remotePeer = init.remotePeer + this.remoteAddr = init.remoteAddr + this.stat = init.stat + this.id = init.id + this.direction = init.direction + this.peerConnection = init.pc; + this.ufrag = init.credential_string; + } + + async newStream(multicodecs: string | string[], options?: AbortOptions): Promise { + log('TODO',this.ufrag); + this.peerConnection.createDataChannel(genUuid()); + throw new Error("not implemented") + } + + addStream(stream: Stream): void { + throw new Error("not implemented") + } + removeStream(id: string): void { + throw new Error("not implemented") + } + async close(): Promise { + throw new Error("not implemented") + } + + private peerConnection: RTCPeerConnection; + private ufrag: string; + +} + diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..cee2fc1 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ + +export {} \ No newline at end of file diff --git a/src/options.ts b/src/options.ts new file mode 100644 index 0000000..ad042b0 --- /dev/null +++ b/src/options.ts @@ -0,0 +1,11 @@ +import {CreateListenerOptions} from '@libp2p/interface-transport' +import { DialOptions } from '@libp2p/interface-transport'; + + +export interface WebRTCListenerOptions extends CreateListenerOptions { //, WebRTCInitiatorInit { +// channelOptions?: WebRTCReceiverInit +} + + +export interface WebRTCDialOptions extends DialOptions { +} diff --git a/src/sdp.ts b/src/sdp.ts new file mode 100644 index 0000000..dd0d33b --- /dev/null +++ b/src/sdp.ts @@ -0,0 +1,65 @@ +import { logger } from '@libp2p/logger' +import { Multiaddr } from '@multiformats/multiaddr' + +const log = logger('libp2p:webrtc:sdp') + +const P_XWEBRTC: number = 0x115; +const SDP_FORMAT: string = ` +v=0 +o=- 0 0 IN %s %s +s=- +c=IN %s %s +t=0 0 +m=application %d UDP/DTLS/SCTP webrtc-datachannel +a=mid:0 +a=ice-options:ice2 +a=ice-ufrag:%s +a=ice-pwd:%s +a=fingerprint:%s +a=setup:actpass +a=sctp-port:5000 +a=max-message-size:100000 +`; + +function ipv(ma: Multiaddr): string { + for ( let proto of ma.protoNames() ) { + if ( proto.startsWith('ip') ) { + return proto.toUpperCase(); + } + } + log("Warning: multiaddr does not appear to contain IP4 or IP6.",ma); + return "IP6"; +} +function ip(ma: Multiaddr): string { + return ma.toOptions().host; +} +function port(ma: Multiaddr): number { + return ma.toOptions().port; +} +function certhash(ma: Multiaddr): string { + let webrtc_value = ma.stringTuples().filter(tup => tup[0] == P_XWEBRTC).map(tup => tup[1])[0]; + if (webrtc_value) { + return webrtc_value.split('/')[1]; + } else { + throw new Error("Couldn't find a webrtc component of multiaddr:"+ma.toString()); + } +} + +function ma2sdp(ma: Multiaddr, ufrag: string): string { + return SDP_FORMAT.replace('/%s/', ipv(ma)) + .replace('/%s/', ip(ma)) + .replace('/%s/', ipv(ma)) + .replace('/%s/', ip(ma)) + .replace('/%s/', port(ma).toString()) + .replace('/%s/', ufrag) + .replace('/%s/', ufrag) + .replace('/%s/', certhash(ma)) + ; +} + +export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { + return { + type: "offer", + sdp: ma2sdp(ma,ufrag) + }; +} diff --git a/src/socket.ts b/src/socket.ts new file mode 100644 index 0000000..a3317fd --- /dev/null +++ b/src/socket.ts @@ -0,0 +1,54 @@ +import type { Socket } from 'socket.io-client' + +export interface OfferSignal { + type: 'offer' + sdp: string +} + +export interface AnswerSignal { + type: 'answer' + sdp: string +} + +export interface CandidateSignal { + type: 'candidate' + candidate: { + candidate: string + sdpMLineIndex?: number + sdpMid?: string + } +} + +export interface RenegotiateSignal { + type: 'renegotiate' +} + +export interface GoodbyeSignal { + type: 'goodbye' +} + +export type Signal = OfferSignal | AnswerSignal | CandidateSignal | RenegotiateSignal | GoodbyeSignal + +export interface HandshakeSignal { + srcMultiaddr: string + dstMultiaddr: string + intentId: string + signal: Signal + answer?: boolean + err?: string +} + +interface SocketEvents { + 'ss-handshake': (offer: HandshakeSignal) => void + 'ss-join': (maStr: string) => void + 'ss-leave': (maStr: string) => void + 'ws-peer': (maStr: string) => void + 'ws-handshake': (offer: HandshakeSignal) => void + 'error': (err: Error) => void + 'listening': () => void + 'close': () => void +} + +export interface WebRTCSocket extends Socket { + +} diff --git a/src/stream.ts b/src/stream.ts new file mode 100644 index 0000000..f680a43 --- /dev/null +++ b/src/stream.ts @@ -0,0 +1,70 @@ +import { Stream } from '@libp2p/interface-connection'; +import {StreamStat}from '@libp2p/interface-connection'; +import { logger } from '@libp2p/logger'; +import { Source } from 'it-stream-types'; +import { Sink } from 'it-stream-types'; + +const log = logger('libp2p:webrtc:connection') + + +export class WebRTCStream implements Stream { + + constructor() { + this.id = "TODO"; + this.stat = { + direction: 'outbound', + timeline: { + open: 0, + close: 0 + } + }; + this.metadata = {}; + log('TODO',this.channel?.id); + } + + /** + * Close a stream for reading and writing + */ + close() : void {} + + /** + * Close a stream for reading only + */ + closeRead() : void {} + + /** + * Close a stream for writing only + */ + closeWrite() : void {} + + /** + * Call when a local error occurs, should close the stream for reading and writing + */ + abort(err: Error): void {} + + /** + * Call when a remote error occurs, should close the stream for reading and writing + */ + reset() : void {} + + /** + * Unique identifier for a stream + */ + id: string; + + /** + * Stats about this stream + */ + stat: StreamStat; + + /** + * User defined stream metadata + */ + metadata: Record; + + source: Source = process.stdin;//TODO + sink: Sink> = (x) => new Promise((res,rej) => {});//TODO + + private channel?: RTCDataChannel; + +} diff --git a/src/transport.ts b/src/transport.ts new file mode 100644 index 0000000..60d7fda --- /dev/null +++ b/src/transport.ts @@ -0,0 +1,46 @@ +import { WebRTCDialOptions } from './options'; +//import { fromMultiAddr } from './sdp' +import { Connection } from '@libp2p/interface-connection'; +import {CreateListenerOptions}from '@libp2p/interface-transport' +import {Listener, Transport } from '@libp2p/interface-transport' +import {DialOptions, symbol } from '@libp2p/interface-transport' +import { logger } from '@libp2p/logger' +import { Multiaddr } from '@multiformats/multiaddr'; +import { v4 as genUuid } from 'uuid'; + +const log = logger('libp2p:webrtc:transport') + +export class WebRTCTransport implements Transport { + + async dial(ma: Multiaddr, options: DialOptions): Promise { + const rawConn = this._connect(ma, options); + log('new outbound connection %s', rawConn, genUuid()); + throw new Error("not implemented"); + } + + createListener(options: CreateListenerOptions): Listener { + throw new Error("TODO - replace with an exception more appropriate to the fact that this will not be implemented."); + } + + filter(multiaddrs: Multiaddr[]): Multiaddr[] { + return [] + }; + + get [Symbol.toStringTag](): string { + return '@libp2p/webrtc' + } + + get [symbol](): true { + return true + } + + todo_cb() { + } + + _connect (ma: Multiaddr, options: WebRTCDialOptions) { + //let peerConnection = new RTCPeerConnection(); + //let handshakeChannel = peerConnection.createDataChannel("data", {negotiated: true, id: 1} ); + throw new Error("not implemented") + } + +} diff --git a/test/node.ts b/test/node.ts new file mode 100644 index 0000000..5c78a05 --- /dev/null +++ b/test/node.ts @@ -0,0 +1,6 @@ +/* eslint-env mocha */ + +export {} + +describe('noop', () => { +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9fff416 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "aegir/src/config/tsconfig.aegir.json", + "compilerOptions": { + "outDir": "dist", + "emitDeclarationOnly": false, + "module": "ES2020", + "importsNotUsedAsValues": "preserve" + }, + "include": [ + "src", + "test" + ] +} From 73b8365181c6d6ef452a61a93e6e4719d5a6c913 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Tue, 2 Aug 2022 20:25:17 -0400 Subject: [PATCH 2/5] Some changes from David's comments. --- .gitignore | 5 +++- .prettierrc | 23 +++++++++++++++++ .vscode/launch.json | 15 ----------- package.json | 5 ++-- src/connection.ts | 5 ++-- src/socket.ts | 54 --------------------------------------- src/transport.ts | 9 +++---- test/{node.ts => node.js} | 0 8 files changed, 36 insertions(+), 80 deletions(-) create mode 100644 .prettierrc delete mode 100644 .vscode/launch.json delete mode 100644 src/socket.ts rename test/{node.ts => node.js} (100%) diff --git a/.gitignore b/.gitignore index b74316f..d15b512 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ bower_components # Editors .idea *.iml +.vscode/launch.json # OS metadata .DS_Store @@ -39,4 +40,6 @@ dist/**/* # ignore yarn.lock yarn.lock -package-lock.json \ No newline at end of file +package-lock.json + + diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..7d54595 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,23 @@ +{ + "printWidth":180, + "tabWidth":2, + "useTabs":false, + "semi":true, + "singleQuote":true, + "trailingComma":"es5", + "bracketSpacing":true, + "jsxBracketSameLine":false, + "arrowParens":"always", + "requirePragma":false, + "insertPragma":false, + "proseWrap":"preserve", + "parser":"babel", + "overrides": [ + { + "files": "*.js", + "options": { + "parser": "babel" + } + } + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 7a9dfa0..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-chrome", - "request": "launch", - "name": "Launch Chrome against localhost", - "url": "http://localhost:8080", - "webRoot": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/package.json b/package.json index 782edf4..d5e1334 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ }, "devDependencies": { "@types/uuid": "^8.3.4", - "aegir": "^37.4.6" + "aegir": "^37.4.6", + "prettier": "^2.7.1", + "typescript": "^4.7.4" }, "dependencies": { "@libp2p/components": "^2.0.1", @@ -21,7 +23,6 @@ "@libp2p/logger": "^2.0.0", "abortable-iterator": "^4.0.2", "socket.io-client": "^4.1.2", - "typescript": "^4.7.4", "uuid": "^8.3.2" } } diff --git a/src/connection.ts b/src/connection.ts index 5da11a2..d130a9a 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -31,6 +31,8 @@ export class WebRTCConnection implements Connection { streams: Stream[] = []; direction: Direction; + private peerConnection: RTCPeerConnection; + private ufrag: string; constructor(init: ConnectionInit) { this.streams = [] @@ -59,8 +61,5 @@ export class WebRTCConnection implements Connection { throw new Error("not implemented") } - private peerConnection: RTCPeerConnection; - private ufrag: string; - } diff --git a/src/socket.ts b/src/socket.ts deleted file mode 100644 index a3317fd..0000000 --- a/src/socket.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Socket } from 'socket.io-client' - -export interface OfferSignal { - type: 'offer' - sdp: string -} - -export interface AnswerSignal { - type: 'answer' - sdp: string -} - -export interface CandidateSignal { - type: 'candidate' - candidate: { - candidate: string - sdpMLineIndex?: number - sdpMid?: string - } -} - -export interface RenegotiateSignal { - type: 'renegotiate' -} - -export interface GoodbyeSignal { - type: 'goodbye' -} - -export type Signal = OfferSignal | AnswerSignal | CandidateSignal | RenegotiateSignal | GoodbyeSignal - -export interface HandshakeSignal { - srcMultiaddr: string - dstMultiaddr: string - intentId: string - signal: Signal - answer?: boolean - err?: string -} - -interface SocketEvents { - 'ss-handshake': (offer: HandshakeSignal) => void - 'ss-join': (maStr: string) => void - 'ss-leave': (maStr: string) => void - 'ws-peer': (maStr: string) => void - 'ws-handshake': (offer: HandshakeSignal) => void - 'error': (err: Error) => void - 'listening': () => void - 'close': () => void -} - -export interface WebRTCSocket extends Socket { - -} diff --git a/src/transport.ts b/src/transport.ts index 60d7fda..0660ecc 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -1,7 +1,6 @@ import { WebRTCDialOptions } from './options'; -//import { fromMultiAddr } from './sdp' import { Connection } from '@libp2p/interface-connection'; -import {CreateListenerOptions}from '@libp2p/interface-transport' +import { CreateListenerOptions}from '@libp2p/interface-transport' import {Listener, Transport } from '@libp2p/interface-transport' import {DialOptions, symbol } from '@libp2p/interface-transport' import { logger } from '@libp2p/logger' @@ -23,15 +22,15 @@ export class WebRTCTransport implements Transport { } filter(multiaddrs: Multiaddr[]): Multiaddr[] { - return [] + return []; }; get [Symbol.toStringTag](): string { - return '@libp2p/webrtc' + return '@libp2p/webrtc'; } get [symbol](): true { - return true + return true; } todo_cb() { diff --git a/test/node.ts b/test/node.js similarity index 100% rename from test/node.ts rename to test/node.js From 5b5b4a0995072d6a75c79efa6902a4888558cf10 Mon Sep 17 00:00:00 2001 From: John Turpish Date: Wed, 3 Aug 2022 10:24:44 -0400 Subject: [PATCH 3/5] some formatting --- .eslintrc.js | 51 +++++++++++++++++++++++++++++++ package.json | 1 + src/stream.ts | 11 ++++--- src/transport.ts | 79 +++++++++++++++++++++++------------------------- 4 files changed, 97 insertions(+), 45 deletions(-) create mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..1d38b75 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,51 @@ +module.exports = { + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + 'eslint-plugin-prettier', + 'autofix', + 'import', + 'compat', + 'prettier', + 'unused-imports', + 'react-perf', + ], + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-var-requires': 0, + '@typescript-eslint/ban-ts-comment': 0, + '@typescript-eslint/no-empty-interface': 0, + '@typescript-eslint/ban-types': 0, + '@typescript-eslint/no-explicit-any': 0, + '@typescript-eslint/no-non-null-assertion': 0, + 'prettier/prettier': 'error', + 'import/order': 'error', + 'function-paren-newline': ['error', 'consistent'], + 'array-callback-return': 0, + '@typescript-eslint/no-unused-vars': 1, + 'function-paren-newline': 0, + 'unused-imports/no-unused-imports-ts': 2, + camelcase: 0, + 'react-hooks/exhaustive-deps': 1, + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': ['error'], + }, + extends: [ + 'react-app', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:import/errors', + 'plugin:import/warnings', + 'plugin:import/typescript', + 'plugin:markdown/recommended', + ], + overrides: [ + { + files: ['**/workers/*.ts'], + rules: { + 'no-restricted-globals': 'off', + }, + }, + ], +}; diff --git a/package.json b/package.json index d5e1334..a44c542 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@types/uuid": "^8.3.4", + "@typescript-eslint/parser": "^5.32.0", "aegir": "^37.4.6", "prettier": "^2.7.1", "typescript": "^4.7.4" diff --git a/src/stream.ts b/src/stream.ts index f680a43..b199834 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -18,8 +18,11 @@ export class WebRTCStream implements Stream { close: 0 } }; - this.metadata = {}; - log('TODO',this.channel?.id); + this.metadata = {} + this.sink = (x) => new Promise((res,rej) => {});//TODO + if(this.dataChannel) { + log('TODO',this.dataChannel.id); + } } /** @@ -63,8 +66,8 @@ export class WebRTCStream implements Stream { metadata: Record; source: Source = process.stdin;//TODO - sink: Sink> = (x) => new Promise((res,rej) => {});//TODO + sink: Sink>; - private channel?: RTCDataChannel; + private dataChannel?: RTCDataChannel; } diff --git a/src/transport.ts b/src/transport.ts index 0660ecc..48ffbf2 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -1,45 +1,42 @@ -import { WebRTCDialOptions } from './options'; -import { Connection } from '@libp2p/interface-connection'; -import { CreateListenerOptions}from '@libp2p/interface-transport' -import {Listener, Transport } from '@libp2p/interface-transport' -import {DialOptions, symbol } from '@libp2p/interface-transport' -import { logger } from '@libp2p/logger' -import { Multiaddr } from '@multiformats/multiaddr'; -import { v4 as genUuid } from 'uuid'; +import { WebRTCDialOptions } from './options'; +import { Connection } from '@libp2p/interface-connection'; +import { CreateListenerOptions } from '@libp2p/interface-transport'; +import { Listener, Transport } from '@libp2p/interface-transport'; +import { DialOptions, symbol } from '@libp2p/interface-transport'; +import { logger } from '@libp2p/logger'; +import { Multiaddr } from '@multiformats/multiaddr'; +import { v4 as genUuid } from 'uuid'; -const log = logger('libp2p:webrtc:transport') +const log = logger('libp2p:webrtc:transport'); export class WebRTCTransport implements Transport { - - async dial(ma: Multiaddr, options: DialOptions): Promise { - const rawConn = this._connect(ma, options); - log('new outbound connection %s', rawConn, genUuid()); - throw new Error("not implemented"); - } - - createListener(options: CreateListenerOptions): Listener { - throw new Error("TODO - replace with an exception more appropriate to the fact that this will not be implemented."); - } - - filter(multiaddrs: Multiaddr[]): Multiaddr[] { - return []; - }; - - get [Symbol.toStringTag](): string { - return '@libp2p/webrtc'; - } - - get [symbol](): true { - return true; - } - - todo_cb() { - } - - _connect (ma: Multiaddr, options: WebRTCDialOptions) { - //let peerConnection = new RTCPeerConnection(); - //let handshakeChannel = peerConnection.createDataChannel("data", {negotiated: true, id: 1} ); - throw new Error("not implemented") - } - + async dial(ma: Multiaddr, options: DialOptions): Promise { + const rawConn = this._connect(ma, options); + log('new outbound connection %s', rawConn, genUuid()); + throw new Error('not implemented'); + } + + createListener(options: CreateListenerOptions): Listener { + throw new Error('TODO - replace with an exception more appropriate to the fact that this will not be implemented.'); + } + + filter(multiaddrs: Multiaddr[]): Multiaddr[] { + return []; + } + + get [Symbol.toStringTag](): string { + return '@libp2p/webrtc'; + } + + get [symbol](): true { + return true; + } + + todo_cb() {} + + _connect(ma: Multiaddr, options: WebRTCDialOptions) { + //let peerConnection = new RTCPeerConnection(); + //let handshakeChannel = peerConnection.createDataChannel("data", {negotiated: true, id: 1} ); + throw new Error('not implemented'); + } } From 7e90dbccc77497c77a8dbcc1fb3ad188dc6319ad Mon Sep 17 00:00:00 2001 From: John Turpish Date: Wed, 3 Aug 2022 10:35:49 -0400 Subject: [PATCH 4/5] Fixing .prettierrc. Thanks Paul. --- .prettierrc | 11 +---- package.json | 3 +- src/connection.ts | 116 +++++++++++++++++++++++----------------------- src/index.ts | 3 +- src/options.ts | 12 ++--- src/sdp.ts | 62 +++++++++++++------------ src/stream.ts | 55 +++++++++++----------- 7 files changed, 124 insertions(+), 138 deletions(-) diff --git a/.prettierrc b/.prettierrc index 7d54595..45dab6f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -10,14 +10,5 @@ "arrowParens":"always", "requirePragma":false, "insertPragma":false, - "proseWrap":"preserve", - "parser":"babel", - "overrides": [ - { - "files": "*.js", - "options": { - "parser": "babel" - } - } - ] + "proseWrap":"preserve" } diff --git a/package.json b/package.json index a44c542..4f96f04 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "type": "module", "scripts": { "build": "aegir build", - "test": "aegir test" + "test": "aegir test", + "format": "prettier --write src/*.ts" }, "devDependencies": { "@types/uuid": "^8.3.4", diff --git a/src/connection.ts b/src/connection.ts index d130a9a..939865f 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1,65 +1,63 @@ -import { Connection } from '@libp2p/interface-connection' -import {ConnectionStat}from '@libp2p/interface-connection' -import { Stream, Direction } from '@libp2p/interface-connection' -import { PeerId } from '@libp2p/interface-peer-id'; -import { AbortOptions }from '@libp2p/interfaces'; -import { logger } from '@libp2p/logger' -import { Multiaddr } from '@multiformats/multiaddr'; -import {v4 as genUuid} from 'uuid'; +import { Connection } from '@libp2p/interface-connection'; +import { ConnectionStat } from '@libp2p/interface-connection'; +import { Stream, Direction } from '@libp2p/interface-connection'; +import { PeerId } from '@libp2p/interface-peer-id'; +import { AbortOptions } from '@libp2p/interfaces'; +import { logger } from '@libp2p/logger'; +import { Multiaddr } from '@multiformats/multiaddr'; +import { v4 as genUuid } from 'uuid'; -const log = logger('libp2p:webrtc:connection') +const log = logger('libp2p:webrtc:connection'); type ConnectionInit = { - id: string - localPeer: PeerId - localAddr?: Multiaddr - remotePeer: PeerId - remoteAddr: Multiaddr - direction: Direction - tags?: string[] - stat: ConnectionStat - pc: RTCPeerConnection - credential_string: string -} + id: string; + localPeer: PeerId; + localAddr?: Multiaddr; + remotePeer: PeerId; + remoteAddr: Multiaddr; + direction: Direction; + tags?: string[]; + stat: ConnectionStat; + pc: RTCPeerConnection; + credential_string: string; +}; export class WebRTCConnection implements Connection { - id: string; - stat: ConnectionStat; - remoteAddr: Multiaddr; - remotePeer: PeerId; - tags: string[] = []; - streams: Stream[] = []; - direction: Direction; - - private peerConnection: RTCPeerConnection; - private ufrag: string; - - constructor(init: ConnectionInit) { - this.streams = [] - this.remotePeer = init.remotePeer - this.remoteAddr = init.remoteAddr - this.stat = init.stat - this.id = init.id - this.direction = init.direction - this.peerConnection = init.pc; - this.ufrag = init.credential_string; - } - - async newStream(multicodecs: string | string[], options?: AbortOptions): Promise { - log('TODO',this.ufrag); - this.peerConnection.createDataChannel(genUuid()); - throw new Error("not implemented") - } - - addStream(stream: Stream): void { - throw new Error("not implemented") - } - removeStream(id: string): void { - throw new Error("not implemented") - } - async close(): Promise { - throw new Error("not implemented") - } - + id: string; + stat: ConnectionStat; + remoteAddr: Multiaddr; + remotePeer: PeerId; + tags: string[] = []; + streams: Stream[] = []; + direction: Direction; + + private peerConnection: RTCPeerConnection; + private ufrag: string; + + constructor(init: ConnectionInit) { + this.streams = []; + this.remotePeer = init.remotePeer; + this.remoteAddr = init.remoteAddr; + this.stat = init.stat; + this.id = init.id; + this.direction = init.direction; + this.peerConnection = init.pc; + this.ufrag = init.credential_string; + } + + async newStream(multicodecs: string | string[], options?: AbortOptions): Promise { + log('TODO', this.ufrag); + this.peerConnection.createDataChannel(genUuid()); + throw new Error('not implemented'); + } + + addStream(stream: Stream): void { + throw new Error('not implemented'); + } + removeStream(id: string): void { + throw new Error('not implemented'); + } + async close(): Promise { + throw new Error('not implemented'); + } } - diff --git a/src/index.ts b/src/index.ts index cee2fc1..cb0ff5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1 @@ - -export {} \ No newline at end of file +export {}; diff --git a/src/options.ts b/src/options.ts index ad042b0..7613aea 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,11 +1,9 @@ -import {CreateListenerOptions} from '@libp2p/interface-transport' +import { CreateListenerOptions } from '@libp2p/interface-transport'; import { DialOptions } from '@libp2p/interface-transport'; - -export interface WebRTCListenerOptions extends CreateListenerOptions { //, WebRTCInitiatorInit { -// channelOptions?: WebRTCReceiverInit +export interface WebRTCListenerOptions extends CreateListenerOptions { + //, WebRTCInitiatorInit { + // channelOptions?: WebRTCReceiverInit } - -export interface WebRTCDialOptions extends DialOptions { -} +export interface WebRTCDialOptions extends DialOptions {} diff --git a/src/sdp.ts b/src/sdp.ts index dd0d33b..570a993 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -1,7 +1,7 @@ -import { logger } from '@libp2p/logger' -import { Multiaddr } from '@multiformats/multiaddr' +import { logger } from '@libp2p/logger'; +import { Multiaddr } from '@multiformats/multiaddr'; -const log = logger('libp2p:webrtc:sdp') +const log = logger('libp2p:webrtc:sdp'); const P_XWEBRTC: number = 0x115; const SDP_FORMAT: string = ` @@ -22,44 +22,46 @@ a=max-message-size:100000 `; function ipv(ma: Multiaddr): string { - for ( let proto of ma.protoNames() ) { - if ( proto.startsWith('ip') ) { - return proto.toUpperCase(); - } + for (let proto of ma.protoNames()) { + if (proto.startsWith('ip')) { + return proto.toUpperCase(); } - log("Warning: multiaddr does not appear to contain IP4 or IP6.",ma); - return "IP6"; + } + log('Warning: multiaddr does not appear to contain IP4 or IP6.', ma); + return 'IP6'; } function ip(ma: Multiaddr): string { - return ma.toOptions().host; + return ma.toOptions().host; } function port(ma: Multiaddr): number { - return ma.toOptions().port; + return ma.toOptions().port; } function certhash(ma: Multiaddr): string { - let webrtc_value = ma.stringTuples().filter(tup => tup[0] == P_XWEBRTC).map(tup => tup[1])[0]; - if (webrtc_value) { - return webrtc_value.split('/')[1]; - } else { - throw new Error("Couldn't find a webrtc component of multiaddr:"+ma.toString()); - } + let webrtc_value = ma + .stringTuples() + .filter((tup) => tup[0] == P_XWEBRTC) + .map((tup) => tup[1])[0]; + if (webrtc_value) { + return webrtc_value.split('/')[1]; + } else { + throw new Error("Couldn't find a webrtc component of multiaddr:" + ma.toString()); + } } function ma2sdp(ma: Multiaddr, ufrag: string): string { - return SDP_FORMAT.replace('/%s/', ipv(ma)) - .replace('/%s/', ip(ma)) - .replace('/%s/', ipv(ma)) - .replace('/%s/', ip(ma)) - .replace('/%s/', port(ma).toString()) - .replace('/%s/', ufrag) - .replace('/%s/', ufrag) - .replace('/%s/', certhash(ma)) - ; + return SDP_FORMAT.replace('/%s/', ipv(ma)) + .replace('/%s/', ip(ma)) + .replace('/%s/', ipv(ma)) + .replace('/%s/', ip(ma)) + .replace('/%s/', port(ma).toString()) + .replace('/%s/', ufrag) + .replace('/%s/', ufrag) + .replace('/%s/', certhash(ma)); } export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { - return { - type: "offer", - sdp: ma2sdp(ma,ufrag) - }; + return { + type: 'offer', + sdp: ma2sdp(ma, ufrag), + }; } diff --git a/src/stream.ts b/src/stream.ts index b199834..90bbb4d 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -1,44 +1,42 @@ -import { Stream } from '@libp2p/interface-connection'; -import {StreamStat}from '@libp2p/interface-connection'; -import { logger } from '@libp2p/logger'; -import { Source } from 'it-stream-types'; -import { Sink } from 'it-stream-types'; - -const log = logger('libp2p:webrtc:connection') +import { Stream } from '@libp2p/interface-connection'; +import { StreamStat } from '@libp2p/interface-connection'; +import { logger } from '@libp2p/logger'; +import { Source } from 'it-stream-types'; +import { Sink } from 'it-stream-types'; +const log = logger('libp2p:webrtc:connection'); export class WebRTCStream implements Stream { - - constructor() { - this.id = "TODO"; - this.stat = { - direction: 'outbound', - timeline: { - open: 0, - close: 0 - } - }; - this.metadata = {} - this.sink = (x) => new Promise((res,rej) => {});//TODO - if(this.dataChannel) { - log('TODO',this.dataChannel.id); - } + constructor() { + this.id = 'TODO'; + this.stat = { + direction: 'outbound', + timeline: { + open: 0, + close: 0, + }, + }; + this.metadata = {}; + this.sink = (x) => new Promise((res, rej) => {}); //TODO + if (this.dataChannel) { + log('TODO', this.dataChannel.id); } + } - /** + /** * Close a stream for reading and writing */ - close() : void {} + close(): void {} /** * Close a stream for reading only */ - closeRead() : void {} + closeRead(): void {} /** * Close a stream for writing only */ - closeWrite() : void {} + closeWrite(): void {} /** * Call when a local error occurs, should close the stream for reading and writing @@ -48,7 +46,7 @@ export class WebRTCStream implements Stream { /** * Call when a remote error occurs, should close the stream for reading and writing */ - reset() : void {} + reset(): void {} /** * Unique identifier for a stream @@ -65,9 +63,8 @@ export class WebRTCStream implements Stream { */ metadata: Record; - source: Source = process.stdin;//TODO + source: Source = process.stdin; //TODO sink: Sink>; private dataChannel?: RTCDataChannel; - } From 6be636439bc592f39abeb7735ffe350052d7330d Mon Sep 17 00:00:00 2001 From: John Turpish Date: Wed, 3 Aug 2022 10:56:30 -0400 Subject: [PATCH 5/5] Pulling in Chinmay's comments from his branch. --- src/connection.ts | 9 +++++++++ src/sdp.ts | 10 ++++++++-- src/transport.ts | 41 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 939865f..00a5cc2 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -43,9 +43,18 @@ export class WebRTCConnection implements Connection { this.direction = init.direction; this.peerConnection = init.pc; this.ufrag = init.credential_string; + // for muxing incoming stream + // this._peerConnection.ondatachannel = ({ channel }) => { + // let stream = DataChannelStream(channel) + // this.addStream(stream) + // } } async newStream(multicodecs: string | string[], options?: AbortOptions): Promise { + // let label = uuid.v4() + // let dc = this._peerConnection.createDataChannel(label, {}) + // await datachannel opening + // return DataChannelStream(dc) log('TODO', this.ufrag); this.peerConnection.createDataChannel(genUuid()); throw new Error('not implemented'); diff --git a/src/sdp.ts b/src/sdp.ts index 570a993..31920ab 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -4,7 +4,7 @@ import { Multiaddr } from '@multiformats/multiaddr'; const log = logger('libp2p:webrtc:sdp'); const P_XWEBRTC: number = 0x115; -const SDP_FORMAT: string = ` +const ANSWER_SDP_FORMAT: string = ` v=0 o=- 0 0 IN %s %s s=- @@ -49,7 +49,7 @@ function certhash(ma: Multiaddr): string { } function ma2sdp(ma: Multiaddr, ufrag: string): string { - return SDP_FORMAT.replace('/%s/', ipv(ma)) + return ANSWER_SDP_FORMAT.replace('/%s/', ipv(ma)) .replace('/%s/', ip(ma)) .replace('/%s/', ipv(ma)) .replace('/%s/', ip(ma)) @@ -65,3 +65,9 @@ export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescripti sdp: ma2sdp(ma, ufrag), }; } + +export function munge(desc: RTCSessionDescription, ufrag: string) { + //TODO + desc.sdp.replaceAll(/^a=ice-ufrag=(.*)/, 'a=ice-ufrag=' + ufrag); + desc.sdp.replaceAll(/^a=ice-pwd=(.*)/, 'a=ice-pwd=' + ufrag); +} diff --git a/src/transport.ts b/src/transport.ts index 48ffbf2..8549e5d 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -36,7 +36,46 @@ export class WebRTCTransport implements Transport { _connect(ma: Multiaddr, options: WebRTCDialOptions) { //let peerConnection = new RTCPeerConnection(); - //let handshakeChannel = peerConnection.createDataChannel("data", {negotiated: true, id: 1} ); + // create data channel + // let handshakeChannel = peerConnection.createDataChannel("data", { negotiated: true, id: 1 }) + // let handshakeChannel = peerConnection.createDataChannel("data", { id: 1 }) + // + // + // create offer sdp + // console.log(offerSdp) + // + // + // generate random string for ufrag + // + // + // + // munge sdp with ufrag = pwd + // + // + // + // set local description + // + // + // + // construct answer sdp from multiaddr + // + // + // + // set remote description + // + // + // + // wait for peerconnection.onopen to fire, or for the datachannel to open + // openPromise = new Promise((res, rej) => { + // dc.onopen = res + // setTimeout(rej, 10000) + // }) + // await openPromise + // + // do noise handshake + webrtc handshake as described in spec + // + // + // return Connection(peerconnection, initoptions) throw new Error('not implemented'); } }