Skip to content

Commit

Permalink
feat: Added WPP.call.end function
Browse files Browse the repository at this point in the history
  • Loading branch information
edgardmessias committed Mar 29, 2023
1 parent aa53282 commit ec7364d
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 43 deletions.
106 changes: 106 additions & 0 deletions src/call/functions/endCall.ts
@@ -0,0 +1,106 @@
/*!
* Copyright 2023 WPPConnect Team
*
* 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.
*/

import { WPPError } from '../../util';
import { CallModel, CallStore, websocket } from '../../whatsapp';
import { CALL_STATES } from '../../whatsapp/enums';

/**
* End a outcoming call
*
* @example
* ```javascript
* // End any outcoming call
* WPP.call.endCall();
*
* // End specific call id
* WPP.call.endCall(callId);
*
* // End any outcoming call
* WPP.on('call.outcoming_call', (call) => {
* WPP.call.endCall(call.id);
* });
* ```
*
* @param {string} callId The call ID, empty to end the first one
* @return {[type]} [return description]
*/
export async function endCall(callId?: string): Promise<boolean> {
const callOut = [
CALL_STATES.ACTIVE,
CALL_STATES.OUTGOING_CALLING,
CALL_STATES.OUTGOING_RING,
];

let call: CallModel | undefined = undefined;

if (callId) {
call = CallStore.get(callId);
} else {
// First outcoming ring or call group
call = CallStore.findFirst(
(c) => callOut.includes(c.getState()) || c.isGroup
);
}

if (!call) {
throw new WPPError(
'call_not_found',
`Call ${callId || '<empty>'} not found`,
{
callId,
}
);
}

if (!callOut.includes(call.getState()) && !call.isGroup) {
throw new WPPError(
'call_is_not_outcoming_calling',
`Call ${callId || '<empty>'} is not outcoming calling`,
{
callId,
state: call.getState(),
}
);
}

if (!call.peerJid.isGroupCall()) {
await websocket.ensureE2ESessions([call.peerJid]);
}

const node = websocket.smax(
'call',
{
to: call.peerJid.toString({ legacy: true }),
id: websocket.generateId(),
},
[
websocket.smax(
'terminate',
{
'call-id': call.id,
'call-creator': call.peerJid.toString({ legacy: true }),
// count: '0',
},
null
),
]
);

await websocket.sendSmaxStanza(node);

return true;
}
1 change: 1 addition & 0 deletions src/call/functions/index.ts
Expand Up @@ -14,5 +14,6 @@
* limitations under the License.
*/

export { endCall } from './endCall';
export { rejectCall } from './rejectCall';
export { sendCallOffer } from './sendCallOffer';
66 changes: 66 additions & 0 deletions src/call/functions/prepareDestination.ts
@@ -0,0 +1,66 @@
/*!
* Copyright 2023 WPPConnect Team
*
* 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.
*/

import { functions, multidevice, websocket, Wid } from '../../whatsapp';

export async function prepareDestionation(wids: Wid[]) {
const fanList = await functions.getFanOutList({ wids });
await websocket.ensureE2ESessions(fanList);

let shouldHaveIdentity = false;
const destination = await Promise.all(
fanList.map(async (wid) => {
const encKey = self.crypto.getRandomValues(new Uint8Array(32)).buffer;

const { type, ciphertext } = await functions.encryptMsgProtobuf(wid, 0, {
call: {
callKey: new Uint8Array(encKey),
},
});

shouldHaveIdentity = shouldHaveIdentity || type === 'pkmsg';

return websocket.smax(
'to',
{
jid: wid.toString({ legacy: true }),
},
[
websocket.smax(
'enc',
{
v: '2',
type: type,
count: '0',
},
ciphertext
),
]
);
})
);

const content: websocket.WapNode[] = [];

content.push(websocket.smax('destination', {}, destination));

if (shouldHaveIdentity) {
const identity = await multidevice.adv.getADVEncodedIdentity();
content.push(websocket.smax('device-identity', undefined, identity));
}

return content;
}
69 changes: 26 additions & 43 deletions src/call/functions/sendCallOffer.ts
Expand Up @@ -17,12 +17,16 @@
import { assertWid } from '../../assert';
import { WPPError } from '../../util';
import {
CallModel,
CallStore,
functions,
multidevice,
UserPrefs,
websocket,
Wid,
} from '../../whatsapp';
import { CALL_STATES } from '../../whatsapp/enums';
import { unixTime } from '../../whatsapp/functions';
import { prepareDestionation } from './prepareDestination';

export interface CallOfferOptions {
isVideo?: boolean;
Expand Down Expand Up @@ -99,47 +103,7 @@ export async function sendCallOffer(
]
);

const fanList = await functions.getFanOutList({ wids: [toWid] });
await websocket.ensureE2ESessions(fanList);

let shouldHaveIdentity = false;
const destination = await Promise.all(
fanList.map(async (wid) => {
const encKey = self.crypto.getRandomValues(new Uint8Array(32)).buffer;

const { type, ciphertext } = await functions.encryptMsgProtobuf(wid, 0, {
call: {
callKey: new Uint8Array(encKey),
},
});

shouldHaveIdentity = shouldHaveIdentity || type === 'pkmsg';

return websocket.smax(
'to',
{
jid: wid.toString({ legacy: true }),
},
[
websocket.smax(
'enc',
{
v: '2',
type: type,
count: '0',
},
ciphertext
),
]
);
})
);
content.push(websocket.smax('destination', {}, destination));

if (shouldHaveIdentity) {
const identity = await multidevice.adv.getADVEncodedIdentity();
content.push(websocket.smax('device-identity', undefined, identity));
}
content.push(...(await prepareDestionation([toWid])));

const node = websocket.smax(
'call',
Expand All @@ -159,7 +123,26 @@ export async function sendCallOffer(
]
);

const model = new CallModel({
id: callId,
peerJid: toWid,
isVideo: options.isVideo,
isGroup: false,
outgoing: true,
offerTime: unixTime(),
webClientShouldHandle: false,
canHandleLocally: true,
});

CallStore.add(model);

CallStore.setActiveCall(CallStore.assertGet(callId));

model.setState(CALL_STATES.OUTGOING_CALLING);

const response = await websocket.sendSmaxStanza(node);

return response;
console.info(response);

return model;
}

0 comments on commit ec7364d

Please sign in to comment.