Skip to content

Commit

Permalink
simplify api
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathaniel Camomot committed Nov 18, 2018
1 parent 4b65dab commit e868d61
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 213 deletions.
90 changes: 89 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,93 @@
firepeer
========

simple-peer signalling through firebase
secure signalling and authentication for [simple-peer](https://github.com/feross/simple-peer) using [firebase realtime database](https://firebase.google.com/docs/database/).

## setup firebase

Follow the instructions here:
1. https://firebase.google.com/docs/web/setup
2. https://firebase.google.com/docs/database/web/start.

Basically, you'll need to create a firebase project and setup the JS client SDK:
```javascript
firebase.initializeApp({
//values from firebase console
});
```

You will also need to add these in your [security rules](https://firebase.google.com/docs/database/security) through the [console](https://console.firebase.google.com).

```javascript
{
"rules": {
"peers": {
"$uid": {
"offers": {
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid",
"$offerId": {
".read": "auth != null && data.child('uid').val() == auth.uid",
".write": "auth != null && !data.exists() && newData.child('uid').val() == auth.uid",
}
}
}
}
}
}
```

## install firepeer
```sh
npm install firepeer
```
or
```html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/lib/firepeer.min.js"></script>
```

## use firepeer
```javascript
//alice side

const cred = await firebase.auth().signInWith**() //sign in with any method as alice;

const firepeer = new FirePeer({
user: cred.user,
ref: firebase.database().ref(`/users/${cred.user.uid}`)
});

// initiate connection
const connection = await alice.connect(firebase.database.ref('users/<uid of bob>'));

// send a mesage to bob
connection.send('hello')
```
```javascript
// bob side

// create a receiver
const cred = await firebase.auth().signInWith**() //sign in with any method as bob;

const firepeer = new FirePeer({
user: cred.user,
ref: firebase.database().ref(`/users/${cred.user.uid}`)
});

// wait for connection and receive message
receiver.on('connection', (connection)=>{
connection.on('data', (data)=>{
console.log(data) //hello
})
})
```
## how this works?
When a reference is passed through `new FirePeer()`, firepeer will create a child name `offers` under that reference and listen to any child_added event on that node. When a reference is passed through `.connect()`, firepeer will create a new child under `offers`.

The rules ensure that only user with $uid, will read and write access

Use firepeer to establish a p2p connection:


> Connections are just instances of [SimplePeer](https://github.com/feross/simple-peer#api) already connected!
3 changes: 2 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
test cleanup
test non-happy path
test non-happy path
validation rules
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "firepeer",
"version": "0.0.8",
"description": "simple-peer signalling through firebase",
"version": "0.0.7",
"description": "easy and secure signalling for simple-peer through firebase",
"main": "lib/firepeer.js",
"browser": "lib/firepeer.min.js",
"typings": "lib/firepeer.d.ts",
Expand All @@ -27,7 +27,7 @@
"test": "run-s clean build test:*",
"test:lint": "tslint --project .",
"test:prettier": "prettier \"src/**/*.ts\" --list-different",
"test:unit": "cross-env DEBUG=firepeer ava lib/test/unit/*.test.js",
"t:unit": "cross-env DEBUG=firepeer ava lib/test/unit/*.test.js",
"test:int": "cross-env DEBUG=firepeer ava lib/test/int/*.test.js",
"doc": "typedoc src/ --target ES6 --mode file --theme markdown --exclude \"**/*+(spec|fixture|test).ts\" --excludeProtected --excludePrivate --out reference",
"version": "standard-version",
Expand Down
2 changes: 1 addition & 1 deletion src/debug.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import * as debug from 'debug';
import debug from 'debug';

export default debug('firepeer');
83 changes: 43 additions & 40 deletions src/firepeer.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,48 @@
import { EventEmitter } from 'events';
import * as firebase from 'firebase';
import firebase from 'firebase';
import * as SimplePeer from 'simple-peer';
import debug from './debug';

export declare interface FirePeer {
on(event: 'connection', listener: (peer: SimplePeer.Instance) => void): this;
on(event: string | 'destroy', listener: () => void): this;
}

export interface FirePeerOptions {
wrtc?: any;
user?: firebase.User;
peersPath?: string;
offersPath?: string;
answerPath?: string;
uidPath?: string;
}

export class FirePeer extends EventEmitter {
private offers: firebase.database.Reference | null;
private offersSub: any;
private ref?: firebase.database.Reference;
private refSub: any;
private wrtc?: any;
private user?: firebase.User;
private user: firebase.User | null | undefined;
private peersPath = 'peers';
private offersPath = 'offers';
private answerPath = 'answer';
private uidPath = 'uid';

constructor(
myRef?: firebase.database.Reference | FirePeerOptions,
options?: FirePeerOptions
) {
constructor(private fb: typeof firebase, options?: FirePeerOptions) {
super();
if (myRef && 'ref' in myRef) {
this.offers = myRef.child(this.offersPath);
Object.assign(this, options);
} else {
this.offers = null;
Object.assign(this, myRef);
}

if (this.offers) {
debug('constructor() listening to %s', this.offers.toString());
this.offersSub = this.offers.on('child_added', offerSnapshot => {
if (offerSnapshot) {
const offer = offerSnapshot.val() as SimplePeer.SignalData;
if (offer) {
this.incoming(offerSnapshot.ref, offer);
}
}
});
}
}

public destroy(): void {
if (this.offers && this.offersSub) {
this.offers.off('child_added', this.offersSub);
}
this.emit('destroy');
Object.assign(this, options);

this.fb.auth().onAuthStateChanged(user => {
this.user = user;
if (this.user) {
this.listen(this.user);
} else {
this.unlisten();
}
});
}

public connect(
otherRef: firebase.database.Reference
): Promise<SimplePeer.Instance> {
const offersRef = otherRef.child(this.offersPath);
public connect(id: string): Promise<SimplePeer.Instance> {
const offersRef = this.fb
.database()
.ref(`${this.peersPath}/${id}/${this.offersPath}`);

debug('connect() %s', offersRef.toString());

Expand Down Expand Up @@ -121,6 +102,28 @@ export class FirePeer extends EventEmitter {
});
}

private listen(user: firebase.User): void {
this.ref = this.fb
.database()
.ref(`${this.peersPath}/${user.uid}/${this.offersPath}`);

debug('listen() to %s', this.ref.toString());
this.refSub = this.ref.on('child_added', offerSnapshot => {
if (offerSnapshot) {
const offer = offerSnapshot.val() as SimplePeer.SignalData;
if (offer) {
this.incoming(offerSnapshot.ref, offer);
}
}
});
}

private unlisten(): void {
if (this.ref && this.refSub) {
this.ref.off('child_added', this.refSub);
}
}

private incoming(
ref: firebase.database.Reference,
offer: SimplePeer.SignalData
Expand Down
2 changes: 1 addition & 1 deletion src/test/int/firebase.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as firebase from 'firebase';
import firebase from 'firebase';

firebase.initializeApp({
apiKey: process.env.API_KEY,
Expand Down
18 changes: 3 additions & 15 deletions src/test/int/firepeer.alice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,13 @@ import { FirePeer } from '../../firepeer';
import firebase from './firebase.fixture';

test.before(async t => {
const alice = firebase.database().ref(`test/${process.env.ALICE_UID}`);
const bob = firebase.database().ref(`test/${process.env.BOB_UID}`);
t.context = {
alice,
bob
};

if (process.env.ALICE_EMAIL && process.env.ALICE_PASS) {
const cred = await firebase
await firebase
.auth()
.signInWithEmailAndPassword(
process.env.ALICE_EMAIL,
process.env.ALICE_PASS
);
(t.context as any).user = cred.user;
}
});

Expand All @@ -29,11 +21,7 @@ test.after(async t => {
});

test.serial('alice tries to connect to bob authenticated', async t => {
const context: any = t.context as any;
const alice = new FirePeer(context.alice, {
user: context.user,
wrtc
});
await alice.connect(context.bob);
const alice = new FirePeer(firebase, { wrtc });
await alice.connect(process.env.BOB_UID as string);
t.pass();
});
14 changes: 2 additions & 12 deletions src/test/int/firepeer.bob.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,10 @@ import { waitConn } from '../utils';
import firebase from './firebase.fixture';

test.before(async t => {
const alice = firebase.database().ref(`test/${process.env.ALICE_UID}`);
const bob = firebase.database().ref(`test/${process.env.BOB_UID}`);
t.context = {
alice,
bob
};

if (process.env.BOB_EMAIL && process.env.BOB_PASS) {
const cred = await firebase
await firebase
.auth()
.signInWithEmailAndPassword(process.env.BOB_EMAIL, process.env.BOB_PASS);

(t.context as any).user = cred.user;
}
});

Expand All @@ -28,8 +19,7 @@ test.after(async t => {
});

test.serial('bob waits for connection from alice authenticated', async t => {
const context: any = t.context as any;
const bob = new FirePeer(context.bob, { wrtc, user: context.user });
const bob = new FirePeer(firebase, { wrtc });
await waitConn(bob);
t.pass();
});
14 changes: 2 additions & 12 deletions src/test/int/firepeer.unauth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,7 @@ import * as wrtc from 'wrtc';
import { FirePeer } from '../../firepeer';
import firebase from './firebase.fixture';

test.before(async t => {
const alice = firebase.database().ref(`test/${process.env.ALICE_UID}`);
const bob = firebase.database().ref(`test/${process.env.BOB_UID}`);
t.context = {
alice,
bob
};
});

test.serial('alice tries to connect to bob unauthenticated', async t => {
const context: any = t.context as any;
const alice = new FirePeer(context.alice, { wrtc });
await t.throwsAsync(alice.connect(context.bob));
const alice = new FirePeer(firebase, { wrtc });
await t.throwsAsync(alice.connect(process.env.BOB_UID as string));
});
58 changes: 29 additions & 29 deletions src/test/unit/firebase.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import * as firebasemock from 'firebase-mock';
// import * as firebasemock from 'firebase-mock';

const mockauth = new firebasemock.MockAuthentication();
const mockdatabase = new firebasemock.MockFirebase();
const mockfirestore = new firebasemock.MockFirestore();
const mockstorage = new firebasemock.MockStorage();
const mockmessaging = new firebasemock.MockMessaging();
const firebase = new firebasemock.MockFirebaseSdk(
// use null if your code does not use RTDB
(path: string) => {
return path ? mockdatabase.child(path) : mockdatabase;
},
// use null if your code does not use AUTHENTICATION
() => {
return mockauth;
},
// use null if your code does not use FIRESTORE
() => {
return mockfirestore;
},
// use null if your code does not use STORAGE
() => {
return mockstorage;
},
// use null if your code does not use MESSAGING
() => {
return mockmessaging;
}
);
// const mockauth = new firebasemock.MockAuthentication();
// const mockdatabase = new firebasemock.MockFirebase();
// const mockfirestore = new firebasemock.MockFirestore();
// const mockstorage = new firebasemock.MockStorage();
// const mockmessaging = new firebasemock.MockMessaging();
// const firebase = new firebasemock.MockFirebaseSdk(
// // use null if your code does not use RTDB
// (path: string) => {
// return path ? mockdatabase.child(path) : mockdatabase;
// },
// // use null if your code does not use AUTHENTICATION
// () => {
// return mockauth;
// },
// // use null if your code does not use FIRESTORE
// () => {
// return mockfirestore;
// },
// // use null if your code does not use STORAGE
// () => {
// return mockstorage;
// },
// // use null if your code does not use MESSAGING
// () => {
// return mockmessaging;
// }
// );

export default firebase;
// export default firebase;
Loading

0 comments on commit e868d61

Please sign in to comment.