Skip to content

Commit

Permalink
feat: add accept request (#707)
Browse files Browse the repository at this point in the history
* feat: add accept request func

* refactor: refactor acceptInvite

* refactor: comments rft

* refactor: rft

* fix: stream array logic fix

* fix: comments fic

* refactor: rm video
  • Loading branch information
arn4b authored Sep 14, 2023
1 parent 464d53f commit 54b215d
Show file tree
Hide file tree
Showing 4 changed files with 351 additions and 99 deletions.
19 changes: 11 additions & 8 deletions packages/restapi/src/lib/spaceV2/SpaceV2.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { produce } from "immer";

import { join } from "./join";
import { acceptInvite } from "./acceptInvite";
import { ISpaceInviteInputOptions, inviteToJoin } from "./inviteToJoin";

import Constants, { ENV } from "../constants";
Expand All @@ -25,14 +26,14 @@ export const initSpaceInfo: SpaceDTO = {
scheduleEnd: null,
status: null,
inviteeDetails: {}
};
};

export const initSpaceV2Data: SpaceV2Data = {
spaceInfo: initSpaceInfo,
meta: {
initiator: {
address: '',
signal: null,
address: '',
signal: null,
},
},
local: {
Expand Down Expand Up @@ -78,7 +79,7 @@ export class SpaceV2 {
protected pgpPrivateKey: string;
protected env: ENV;

private peerConnections: Map<string, RTCPeerConnection> = new Map();
private peerConnections: Map<string, RTCPeerConnection | undefined> = new Map();

protected data!: SpaceV2Data;

Expand All @@ -93,7 +94,7 @@ export class SpaceV2 {
env = Constants.ENV.PROD,
setSpaceV2Data, // to update the 'spaceData' state maintained by the developer
} = options || {};

this.signer = signer;
this.chainId = chainId;
this.pgpPrivateKey = pgpPrivateKey;
Expand All @@ -118,10 +119,10 @@ export class SpaceV2 {
draft.local.address = pCAIP10ToWallet(address);
});
});

// init the state maintained by the developer
setSpaceV2Data(() => initSpaceV2Data);

// init the spaceSpecificData class variable
this.data = initSpaceV2Data;
}
Expand Down Expand Up @@ -150,7 +151,7 @@ export class SpaceV2 {
}

// Set a connected peer's peer connection by their ID
setPeerConnection(peerId: string, peerConnection: RTCPeerConnection) {
setPeerConnection(peerId: string, peerConnection: RTCPeerConnection | undefined) {
this.peerConnections.set(peerId, peerConnection);
}

Expand Down Expand Up @@ -185,4 +186,6 @@ export class SpaceV2 {
}

public join = join;

public acceptInvite = acceptInvite;
}
260 changes: 260 additions & 0 deletions packages/restapi/src/lib/spaceV2/acceptInvite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
/**
* @file acceptInvite
* This file defines functions related to accepting invites
* and managing connections within the SpaceV2 class.
* It includes the `acceptInvite` function,
* which handles incoming invitations and manages peer connections,
* as well as related utility functions.
*/

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import * as Peer from 'simple-peer';
import { produce } from 'immer';

import { initSpaceV2Data, type SpaceV2 } from './SpaceV2';
import sendSpaceNotification from './helpers/sendSpaceNotification';

import { SPACE_ACCEPT_REQUEST_TYPE, SPACE_DISCONNECT_TYPE } from '../payloads/constants';
import { VideoCallStatus } from '../types';

// imports from Video
import getIncomingIndexFromAddress from '../video/helpers/getIncomingIndexFromAddress';
import isJSON from '../video/helpers/isJSON';
import { endStream } from '../video/helpers/mediaToggle';
import { getIceServerConfig } from '../video/helpers/getIceServerConfig';

export interface IAcceptInvite {
signalData: any;
senderAddress: string;
recipientAddress: string;
spaceId: string;
onReceiveMessage?: (message: string) => void;
retry?: boolean;
details?: {
type: SPACE_ACCEPT_REQUEST_TYPE;
data: Record<string, unknown>;
};
}

export async function acceptInvite(
this: SpaceV2,
options: IAcceptInvite
) {
const {
signalData,
senderAddress,
recipientAddress,
spaceId,
onReceiveMessage = (message: string) => {
console.log('received a meesage', message);
},
retry = false,
details,
} = options || {};

try {
console.log('ACCEPT INVITE', options);

// if getPeerConnection is not null -> acceptRequest/request was called before
if (this.getPeerConnection(recipientAddress)) {
// to prevent connection error we stop the exec of acceptRequest
return Promise.resolve();
}

// fetching the iceServers config
const iceServerConfig = await getIceServerConfig(this.env);

let peerConnection = new Peer({
initiator: true,
trickle: false,
stream: this.data.local.stream,
config: {
iceServers: iceServerConfig,
},
});

this.setPeerConnection(recipientAddress, peerConnection);

peerConnection.on('error', (err: any) => {
if (this.data.incomingPeerStreams[0].retryCount >= 5) {
console.log('Max retries exceeded, please try again.');
this.disconnect({ peerAddress: recipientAddress });
}

// retrying in case of connection error
sendSpaceNotification(
{
signer: this.signer,
chainId: this.chainId,
pgpPrivateKey: this.pgpPrivateKey,
},
{
senderAddress,
recipientAddress,
status: VideoCallStatus.RETRY_INITIALIZED,
spaceId: spaceId,
signalData: null,
env: this.env,
}
);
})

peerConnection.signal(signalData);

peerConnection.on('signal', (data: any) => {
this.setSpaceV2Data((oldData) => {
return produce(oldData, (draft) => {
draft.meta.initiator.signal = data;
});
});

sendSpaceNotification(
{
signer: this.signer,
chainId: this.chainId,
pgpPrivateKey: this.pgpPrivateKey,
},
{
senderAddress,
recipientAddress,
status: retry
? VideoCallStatus.RETRY_RECEIVED
: VideoCallStatus.INITIALIZED,
spaceId,
signalData: data,
env: this.env,
callDetails: details,
}
);
});

peerConnection.on('connect', () => {
peerConnection.send(
JSON.stringify({
type: 'isAudioOn',
value: this.data.local.audio,
})
);

// set videoCallInfo state with status connected for the receiver's end
this.setSpaceV2Data((oldData) => {
return produce(oldData, (draft) => {
const pendingIndex = getIncomingIndexFromAddress(
oldData.pendingPeerStreams,
recipientAddress
);
draft.pendingPeerStreams[pendingIndex].status = VideoCallStatus.CONNECTED;
});
});
});

peerConnection.on('data', (data: any) => {
if (isJSON(data)) {
const parsedData = JSON.parse(data);

if (parsedData.type === 'isAudioOn') {
console.log('IS AUDIO ON', parsedData.value)

this.setSpaceV2Data((oldData) => {
return produce(oldData, (draft) => {
let arrayToUpdate = null;

// Check if the peer is in pendingPeerStreams
const indexInPending = draft.pendingPeerStreams.findIndex(peer => peer.address === recipientAddress);
if (indexInPending !== -1) {
arrayToUpdate = draft.pendingPeerStreams;
}

// Check if the peer is in incomingPeerStreams
const indexInIncoming = draft.incomingPeerStreams.findIndex(peer => peer.address === recipientAddress);
if (indexInIncoming !== -1) {
arrayToUpdate = draft.incomingPeerStreams;
}

// If the peer is found in either array, update the property
if (arrayToUpdate) {
arrayToUpdate[indexInIncoming !== -1 ? indexInIncoming : indexInPending].audio = parsedData.value;
}
});
});
}

if (parsedData.type === 'endCall') {
console.log('END CALL');

if (
parsedData?.details?.type === SPACE_DISCONNECT_TYPE.LEAVE
) {
// destroy connection to only the current peer
peerConnection?.destroy();
peerConnection = null;
this.setSpaceV2Data((oldData) => {
return produce(oldData, (draft) => {
const incomingIndex = getIncomingIndexFromAddress(
oldData.incomingPeerStreams,
recipientAddress
);
draft.incomingPeerStreams.splice(incomingIndex, 1);
});
});
}
if (
parsedData?.details?.type === SPACE_DISCONNECT_TYPE.STOP
) {
// destroy connection to all the peers
for (const connectedAddress in this.getConnectedPeerIds()) {
// TODO: refactor
// this.getPeerConnection(connectedAddress)?.destroy();
this.setPeerConnection(connectedAddress, undefined);
}
}

if (
parsedData?.details?.type === SPACE_DISCONNECT_TYPE.STOP
) {
// destroy the local stream
if (this.data.local.stream) {
endStream(this.data.local.stream);
}

// reset the state
this.setSpaceV2Data(() => initSpaceV2Data);
}
}
} else {
onReceiveMessage(data);
}
});

peerConnection.on(
'stream',
(currentStream: MediaStream) => {
console.log('received incoming stream', currentStream);
const pendingStreamIndex = getIncomingIndexFromAddress(
this.data.pendingPeerStreams,
recipientAddress
);

// Here, we can handle if we want to merge stream or anything
// this.onReceiveStream(
// currentStream,
// recipientAddress,
// this.data.pendingPeerStreams[pendingIndex].audio
// );

// remove stream from pendingPeerStreams and add it to incomingPeerStreams
this.setSpaceV2Data((oldData) => {
return produce(oldData, (draft) => {
const peerStream = draft.pendingPeerStreams[pendingStreamIndex];
peerStream.stream = currentStream;
draft.incomingPeerStreams.push(peerStream);
draft.pendingPeerStreams.splice(pendingStreamIndex, 1);
});
});
}
);
} catch (error) {
console.log('error in acceptInvite', error);
}
}
Loading

0 comments on commit 54b215d

Please sign in to comment.