@signalapp/ringrtc 2.23.1 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,7 +51,8 @@ export declare class RingRTCType {
51
51
  private _callInfoByCallId;
52
52
  private getCallInfoKey;
53
53
  handleOutgoingSignaling: ((remoteUserId: UserId, message: CallingMessage) => Promise<boolean>) | null;
54
- handleIncomingCall: ((call: Call) => Promise<CallSettings | null>) | null;
54
+ handleIncomingCall: ((call: Call) => Promise<boolean>) | null;
55
+ handleStartCall: ((call: Call) => Promise<boolean>) | null;
55
56
  handleAutoEndedIncomingCallRequest: ((callId: CallId, remoteUserId: UserId, reason: CallEndedReason, ageSec: number, wasVideoCall: boolean, receivedAtCounter: number | undefined) => void) | null;
56
57
  handleLogMessage: ((level: CallLogLevel, fileName: string, line: number, message: string) => void) | null;
57
58
  handleSendHttpRequest: ((requestId: number, url: string, method: HttpMethod, headers: {
@@ -63,11 +64,11 @@ export declare class RingRTCType {
63
64
  constructor();
64
65
  setConfig(config: Config): void;
65
66
  setSelfUuid(uuid: Buffer): void;
66
- startOutgoingCall(remoteUserId: UserId, isVideoCall: boolean, localDeviceId: DeviceId, settings: CallSettings): Call;
67
+ startOutgoingCall(remoteUserId: UserId, isVideoCall: boolean, localDeviceId: DeviceId): Call;
67
68
  cancelGroupRing(groupId: GroupId, ringId: bigint, reason: RingCancelReason | null): void;
68
69
  onStartOutgoingCall(remoteUserId: UserId, callId: CallId): void;
69
70
  onStartIncomingCall(remoteUserId: UserId, callId: CallId, isVideoCall: boolean): void;
70
- private proceed;
71
+ proceed(callId: CallId, settings: CallSettings): void;
71
72
  onCallState(remoteUserId: UserId, state: CallState): void;
72
73
  onCallEnded(remoteUserId: UserId, callId: CallId, reason: CallEndedReason, ageSec: number): void;
73
74
  onRemoteVideoEnabled(remoteUserId: UserId, enabled: boolean): void;
@@ -154,7 +155,6 @@ export declare class Call {
154
155
  callId: CallId;
155
156
  private readonly _isIncoming;
156
157
  private readonly _isVideoCall;
157
- settings: CallSettings | null;
158
158
  private _state;
159
159
  private _outgoingAudioEnabled;
160
160
  private _outgoingVideoEnabled;
@@ -173,7 +173,7 @@ export declare class Call {
173
173
  handleNetworkRouteChanged?: () => void;
174
174
  handleAudioLevels?: () => void;
175
175
  renderVideoFrame?: (width: number, height: number, buffer: Buffer) => void;
176
- constructor(callManager: CallManager, remoteUserId: UserId, callId: CallId, isIncoming: boolean, isVideoCall: boolean, settings: CallSettings | null, state: CallState);
176
+ constructor(callManager: CallManager, remoteUserId: UserId, callId: CallId, isIncoming: boolean, isVideoCall: boolean, state: CallState);
177
177
  get remoteUserId(): UserId;
178
178
  get isIncoming(): boolean;
179
179
  get isVideoCall(): boolean;
@@ -3,6 +3,25 @@
3
3
  // Copyright 2019-2021 Signal Messenger, LLC
4
4
  // SPDX-License-Identifier: AGPL-3.0-only
5
5
  //
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
6
25
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
26
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
27
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -15,8 +34,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
15
34
  Object.defineProperty(exports, "__esModule", { value: true });
16
35
  exports.CallLogLevel = exports.CallEndedReason = exports.CallState = exports.RingCancelReason = exports.BandwidthMode = exports.HangupType = exports.OpaqueMessage = exports.HangupMessage = exports.BusyMessage = exports.IceCandidateMessage = exports.AnswerMessage = exports.OfferType = exports.OfferMessage = exports.CallingMessage = exports.GroupCall = exports.VideoRequest = exports.GroupMemberInfo = exports.RemoteDeviceState = exports.LocalDeviceState = exports.HttpMethod = exports.RingUpdate = exports.CallMessageUrgency = exports.GroupCallEndReason = exports.JoinState = exports.ConnectionState = exports.Call = exports.RingRTCType = exports.ReceivedAudioLevel = exports.NetworkRoute = exports.PeekInfo = exports.PeekDeviceInfo = void 0;
17
36
  /* tslint:disable max-classes-per-file */
18
- const os = require("os");
19
- const process = require("process");
37
+ const os = __importStar(require("os"));
38
+ const process = __importStar(require("process"));
20
39
  // tslint:disable-next-line no-var-requires no-require-imports
21
40
  const Native = require('../../build/' +
22
41
  os.platform() +
@@ -210,6 +229,7 @@ class RingRTCType {
210
229
  // Set by UX
211
230
  this.handleOutgoingSignaling = null;
212
231
  this.handleIncomingCall = null;
232
+ this.handleStartCall = null;
213
233
  this.handleAutoEndedIncomingCallRequest = null;
214
234
  this.handleLogMessage = null;
215
235
  this.handleSendHttpRequest = null;
@@ -234,10 +254,10 @@ class RingRTCType {
234
254
  this.callManager.setSelfUuid(uuid);
235
255
  }
236
256
  // Called by UX
237
- startOutgoingCall(remoteUserId, isVideoCall, localDeviceId, settings) {
257
+ startOutgoingCall(remoteUserId, isVideoCall, localDeviceId) {
238
258
  const callId = this.callManager.createOutgoingCall(remoteUserId, isVideoCall, localDeviceId);
239
259
  const isIncoming = false;
240
- const call = new Call(this.callManager, remoteUserId, callId, isIncoming, isVideoCall, settings, CallState.Prering);
260
+ const call = new Call(this.callManager, remoteUserId, callId, isIncoming, isVideoCall, CallState.Prering);
241
261
  this._call = call;
242
262
  // We won't actually send anything until the remote side accepts.
243
263
  call.outgoingAudioEnabled = true;
@@ -253,11 +273,26 @@ class RingRTCType {
253
273
  // Called by Rust
254
274
  onStartOutgoingCall(remoteUserId, callId) {
255
275
  const call = this._call;
256
- if (!call || call.remoteUserId !== remoteUserId || !call.settings) {
276
+ if (!call || call.remoteUserId !== remoteUserId) {
257
277
  return;
258
278
  }
259
279
  call.callId = callId;
260
- this.proceed(callId, call.settings);
280
+ const handleStartCall = this.handleStartCall;
281
+ if (!handleStartCall) {
282
+ call.ignore();
283
+ return;
284
+ }
285
+ handleStartCall(call)
286
+ .then(result => {
287
+ if (!result) {
288
+ this.logWarn('RingRTC.handleStartCall failed for outgoing call. Call ignored.');
289
+ call.ignore();
290
+ }
291
+ })
292
+ .catch(e => {
293
+ this.logError('RingRTC.handleStartCall exception: ' + e.toString());
294
+ call.ignore();
295
+ });
261
296
  }
262
297
  // Called by Rust
263
298
  onStartIncomingCall(remoteUserId, callId, isVideoCall) {
@@ -280,24 +315,38 @@ class RingRTCType {
280
315
  return;
281
316
  }
282
317
  const isIncoming = true;
283
- const call = new Call(this.callManager, remoteUserId, callId, isIncoming, isVideoCall, null, CallState.Prering);
284
- // Callback to UX not set
318
+ const call = new Call(this.callManager, remoteUserId, callId, isIncoming, isVideoCall, CallState.Prering);
285
319
  const handleIncomingCall = this.handleIncomingCall;
286
- if (!handleIncomingCall) {
320
+ const handleStartCall = this.handleStartCall;
321
+ if (!handleIncomingCall || !handleStartCall) {
287
322
  call.ignore();
288
323
  return;
289
324
  }
290
325
  this._call = call;
291
- // tslint:disable no-floating-promises
292
- (() => __awaiter(this, void 0, void 0, function* () {
293
- const settings = yield handleIncomingCall(call);
294
- if (!settings) {
326
+ handleIncomingCall(call)
327
+ .then(success => {
328
+ if (!success) {
329
+ this.logWarn('RingRTC.handleIncomingCall failed for incoming call. Call ignored.');
295
330
  call.ignore();
296
- return;
297
331
  }
298
- call.settings = settings;
299
- this.proceed(callId, settings);
300
- }))();
332
+ else {
333
+ handleStartCall(call)
334
+ .then(success => {
335
+ if (!success) {
336
+ this.logWarn('RingRTC.handleStartCall failed for incoming call. Call ignored.');
337
+ call.ignore();
338
+ }
339
+ })
340
+ .catch(e => {
341
+ this.logError('RingRTC.handleStartCall exception: ' + e.toString());
342
+ call.ignore();
343
+ });
344
+ }
345
+ })
346
+ .catch(e => {
347
+ this.logError('RingRTC.handleIncomingCall exception: ' + e.toString());
348
+ call.ignore();
349
+ });
301
350
  }
302
351
  proceed(callId, settings) {
303
352
  silly_deadlock_protection(() => {
@@ -864,7 +913,7 @@ class RingRTCType {
864
913
  }
865
914
  exports.RingRTCType = RingRTCType;
866
915
  class Call {
867
- constructor(callManager, remoteUserId, callId, isIncoming, isVideoCall, settings, state) {
916
+ constructor(callManager, remoteUserId, callId, isIncoming, isVideoCall, state) {
868
917
  this._outgoingAudioEnabled = false;
869
918
  this._outgoingVideoEnabled = false;
870
919
  this._outgoingVideoIsScreenShare = false;
@@ -880,7 +929,6 @@ class Call {
880
929
  this.callId = callId;
881
930
  this._isIncoming = isIncoming;
882
931
  this._isVideoCall = isVideoCall;
883
- this.settings = settings;
884
932
  this._state = state;
885
933
  }
886
934
  get remoteUserId() {
@@ -144,6 +144,7 @@ class GumVideoCapturer {
144
144
  chromeMediaSourceId: options.screenShareSourceId,
145
145
  maxWidth: options.maxWidth,
146
146
  maxHeight: options.maxHeight,
147
+ minFrameRate: 1,
147
148
  maxFrameRate: options.maxFramerate,
148
149
  },
149
150
  };
@@ -0,0 +1,25 @@
1
+ export declare class CallingClass {
2
+ private _name;
3
+ private _id;
4
+ private _localDeviceId;
5
+ private _call;
6
+ private _delayIncomingCallSettingsRequest;
7
+ private _delayOutgoingCallSettingsRequest;
8
+ set delayIncomingCallSettingsRequest(value: number);
9
+ set delayOutgoingCallSettingsRequest(value: number);
10
+ constructor(name: string, id: string);
11
+ private setupCallCallbacks;
12
+ private handleOutgoingSignaling;
13
+ private handleIncomingCall;
14
+ private handleStartCall;
15
+ private handleAutoEndedIncomingCallRequest;
16
+ private handleLogMessage;
17
+ private handleSendHttpRequest;
18
+ private handleSendCallMessage;
19
+ private handleSendCallMessageToGroup;
20
+ private handleGroupCallRingUpdate;
21
+ private getCallSettings;
22
+ initialize(): void;
23
+ startOutgoingDirectCall(remoteUserId: string): Promise<void>;
24
+ hangup(): boolean;
25
+ }
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2023 Signal Messenger, LLC
4
+ // SPDX-License-Identifier: AGPL-3.0-only
5
+ //
6
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
+ return new (P || (P = Promise))(function (resolve, reject) {
9
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
13
+ });
14
+ };
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.CallingClass = void 0;
20
+ const Service_1 = require("../ringrtc/Service");
21
+ const index_1 = require("../index");
22
+ const long_1 = __importDefault(require("long"));
23
+ const Utils_1 = require("./Utils");
24
+ // This class mimics the Desktop Client CallingClass in ts/services/calling.ts to facilitate testing
25
+ class CallingClass {
26
+ constructor(name, id) {
27
+ this._name = name;
28
+ this._id = id;
29
+ this._localDeviceId = 1;
30
+ this._delayIncomingCallSettingsRequest = 0;
31
+ this._delayOutgoingCallSettingsRequest = 0;
32
+ }
33
+ set delayIncomingCallSettingsRequest(value) {
34
+ this._delayIncomingCallSettingsRequest = value;
35
+ }
36
+ set delayOutgoingCallSettingsRequest(value) {
37
+ this._delayIncomingCallSettingsRequest = value;
38
+ }
39
+ setupCallCallbacks(call) {
40
+ // eslint-disable-next-line no-param-reassign
41
+ call.handleStateChanged = () => __awaiter(this, void 0, void 0, function* () {
42
+ (0, Utils_1.log)('handleCallStateChanged');
43
+ (0, Utils_1.log)(`call.state === ${call.state}`);
44
+ if (call.state === Service_1.CallState.Ended) {
45
+ (0, Utils_1.log)(`call.endedReason === ${call.endedReason}`);
46
+ this._call = undefined;
47
+ }
48
+ });
49
+ // eslint-disable-next-line no-param-reassign
50
+ call.handleRemoteVideoEnabled = () => {
51
+ (0, Utils_1.log)('handleRemoteVideoEnabled');
52
+ };
53
+ // eslint-disable-next-line no-param-reassign
54
+ call.handleRemoteSharingScreen = () => {
55
+ (0, Utils_1.log)('handleRemoteSharingScreen');
56
+ };
57
+ }
58
+ ////////////////////////////////////////////////////////////////////////////////
59
+ // Callbacks
60
+ handleOutgoingSignaling(remoteUserId, message, urgency) {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ (0, Utils_1.log)('handleOutgoingSignaling remoteUserId: ' + remoteUserId);
63
+ return true;
64
+ });
65
+ }
66
+ handleIncomingCall(call) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ (0, Utils_1.log)('handleIncomingCall');
69
+ this._call = call;
70
+ this.setupCallCallbacks(call);
71
+ return true;
72
+ });
73
+ }
74
+ handleStartCall(call) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ const callSettings = yield this.getCallSettings(call.isIncoming);
77
+ index_1.RingRTC.proceed(call.callId, callSettings);
78
+ return true;
79
+ });
80
+ }
81
+ handleAutoEndedIncomingCallRequest(callId, remoteUserId, reason, ageInSeconds, wasVideoCall, receivedAtCounter) {
82
+ return __awaiter(this, void 0, void 0, function* () {
83
+ (0, Utils_1.log)('handleAutoEndedIncomingCallRequest');
84
+ });
85
+ }
86
+ handleLogMessage(level, fileName, line, message) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ switch (level) {
89
+ case Service_1.CallLogLevel.Info:
90
+ // FgGray
91
+ console.log(`\x1b[90m${fileName}:${line} ${message}\x1b[0m`);
92
+ break;
93
+ case Service_1.CallLogLevel.Warn:
94
+ // FgYellow
95
+ console.warn(`\x1b[33m${fileName}:${line} ${message}\x1b[0m`);
96
+ break;
97
+ case Service_1.CallLogLevel.Error:
98
+ // FgRed
99
+ console.error(`\x1b[31m${fileName}:${line} ${message}\x1b[0m`);
100
+ break;
101
+ default:
102
+ break;
103
+ }
104
+ });
105
+ }
106
+ handleSendHttpRequest(requestId, url, method, headers, body) {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ (0, Utils_1.log)('handleSendHttpRequest');
109
+ });
110
+ }
111
+ handleSendCallMessage(recipient, data, urgency) {
112
+ return __awaiter(this, void 0, void 0, function* () {
113
+ (0, Utils_1.log)('handleSendCallMessage');
114
+ return true;
115
+ });
116
+ }
117
+ handleSendCallMessageToGroup(groupIdBytes, data, urgency) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ (0, Utils_1.log)('handleSendCallMessageToGroup');
120
+ });
121
+ }
122
+ handleGroupCallRingUpdate(groupIdBytes, ringId, ringerBytes, update) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ (0, Utils_1.log)('handleGroupCallRingUpdate');
125
+ });
126
+ }
127
+ ////////////////////////////////////////////////////////////////////////////////
128
+ // Support
129
+ getCallSettings(isIncoming) {
130
+ return __awaiter(this, void 0, void 0, function* () {
131
+ if (isIncoming) {
132
+ (0, Utils_1.log)('getCallSettings delayed by ' +
133
+ this._delayIncomingCallSettingsRequest.toString() +
134
+ 'ms');
135
+ yield (0, Utils_1.sleep)(this._delayIncomingCallSettingsRequest);
136
+ }
137
+ else {
138
+ (0, Utils_1.log)('getCallSettings delayed by ' +
139
+ this._delayOutgoingCallSettingsRequest.toString() +
140
+ 'ms');
141
+ yield (0, Utils_1.sleep)(this._delayOutgoingCallSettingsRequest);
142
+ }
143
+ return {
144
+ iceServer: {
145
+ urls: ['stun:turn3.voip.signal.org'],
146
+ },
147
+ hideIp: false,
148
+ bandwidthMode: Service_1.BandwidthMode.Normal,
149
+ };
150
+ });
151
+ }
152
+ ////////////////////////////////////////////////////////////////////////////////
153
+ // Actions
154
+ initialize() {
155
+ (0, Utils_1.log)('initialize');
156
+ index_1.RingRTC.setConfig({
157
+ use_new_audio_device_module: true,
158
+ field_trials: undefined,
159
+ });
160
+ index_1.RingRTC.handleOutgoingSignaling = this.handleOutgoingSignaling.bind(this);
161
+ index_1.RingRTC.handleIncomingCall = this.handleIncomingCall.bind(this);
162
+ index_1.RingRTC.handleStartCall = this.handleStartCall.bind(this);
163
+ index_1.RingRTC.handleAutoEndedIncomingCallRequest =
164
+ this.handleAutoEndedIncomingCallRequest.bind(this);
165
+ index_1.RingRTC.handleLogMessage = this.handleLogMessage.bind(this);
166
+ index_1.RingRTC.handleSendHttpRequest = this.handleSendHttpRequest.bind(this);
167
+ index_1.RingRTC.handleSendCallMessage = this.handleSendCallMessage.bind(this);
168
+ index_1.RingRTC.handleSendCallMessageToGroup =
169
+ this.handleSendCallMessageToGroup.bind(this);
170
+ index_1.RingRTC.handleGroupCallRingUpdate =
171
+ this.handleGroupCallRingUpdate.bind(this);
172
+ index_1.RingRTC.setSelfUuid(Buffer.from((0, Utils_1.uuidToBytes)(this._id)));
173
+ }
174
+ startOutgoingDirectCall(remoteUserId) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ (0, Utils_1.log)('startOutgoingDirectCall');
177
+ if (index_1.RingRTC.call && index_1.RingRTC.call.state !== Service_1.CallState.Ended) {
178
+ (0, Utils_1.log)('Call already in progress, new call not allowed.');
179
+ return;
180
+ }
181
+ const call = index_1.RingRTC.startOutgoingCall(remoteUserId, false, this._localDeviceId);
182
+ (0, Utils_1.log)('Outgoing callId ' + long_1.default.fromValue(call.callId).toString());
183
+ index_1.RingRTC.setOutgoingAudio(call.callId, true);
184
+ this._call = call;
185
+ this.setupCallCallbacks(call);
186
+ });
187
+ }
188
+ hangup() {
189
+ (0, Utils_1.log)('hangup');
190
+ if (this._call) {
191
+ index_1.RingRTC.hangup(this._call.callId);
192
+ return true;
193
+ }
194
+ return false;
195
+ }
196
+ }
197
+ exports.CallingClass = CallingClass;
@@ -12,15 +12,46 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
12
12
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13
13
  });
14
14
  };
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
15
18
  Object.defineProperty(exports, "__esModule", { value: true });
16
19
  const chai_1 = require("chai");
17
- const chaiAsPromised = require("chai-as-promised");
20
+ const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
21
+ const crypto_1 = require("crypto");
18
22
  const index_1 = require("../index");
19
- (0, chai_1.use)(chaiAsPromised);
23
+ const long_1 = __importDefault(require("long"));
24
+ const chai_2 = require("chai");
25
+ const sinon_1 = __importDefault(require("sinon"));
26
+ const sinon_chai_1 = __importDefault(require("sinon-chai"));
27
+ const CallingClass_1 = require("./CallingClass");
28
+ const Utils_1 = require("./Utils");
29
+ (0, chai_1.use)(chai_as_promised_1.default);
30
+ (0, chai_2.should)();
31
+ (0, chai_1.use)(sinon_chai_1.default);
32
+ function generateOfferCallingMessage(callId) {
33
+ // Audio-only hex based SDP generated from a direct client call
34
+ const audioOnlySdp = Buffer.from('22560a204b18bc751315cb718c643db7b3a65aaabe826c7094932afaf5aebc86d36bb6491204484b6b481a18524b3041496f63334245514e5670424b57786f38787051712204082e1034220408281034220208082880897a', 'hex');
35
+ return {
36
+ offer: {
37
+ callId: callId,
38
+ opaque: audioOnlySdp,
39
+ type: index_1.OfferType.AudioCall,
40
+ },
41
+ };
42
+ }
20
43
  describe('RingRTC', () => {
21
- it('testsInitialization', () => {
22
- chai_1.assert.isNotNull(index_1.RingRTC, "RingRTC didn't initialize!");
23
- });
44
+ const identity_key_length = 31;
45
+ const user1_name = 'user1';
46
+ const user1_id = '11';
47
+ const user1_device_id = 11;
48
+ const user1_identity_key = (0, crypto_1.randomBytes)(identity_key_length);
49
+ const user2_id = '22';
50
+ const user2_device_id = 22;
51
+ const user2_identity_key = (0, crypto_1.randomBytes)(identity_key_length);
52
+ let handleOutgoingSignalingSpy;
53
+ let handleIncomingCallSpy;
54
+ let handleAutoEndedIncomingCallRequestSpy;
24
55
  it('reports an age for expired offers', () => __awaiter(void 0, void 0, void 0, function* () {
25
56
  const offer = {
26
57
  offer: {
@@ -68,4 +99,113 @@ describe('RingRTC', () => {
68
99
  index_1.RingRTC.handleAutoEndedIncomingCallRequest = null;
69
100
  }
70
101
  }));
102
+ function initializeSpies() {
103
+ handleAutoEndedIncomingCallRequestSpy = sinon_1.default.spy(index_1.RingRTC, 'handleAutoEndedIncomingCallRequest');
104
+ handleIncomingCallSpy = sinon_1.default.spy(index_1.RingRTC, 'handleIncomingCall');
105
+ handleOutgoingSignalingSpy = sinon_1.default.spy(index_1.RingRTC, 'handleOutgoingSignaling');
106
+ }
107
+ it('can initialize RingRTC', () => {
108
+ chai_1.assert.isNotNull(index_1.RingRTC, "RingRTC didn't initialize!");
109
+ });
110
+ it('can establish outgoing call', () => __awaiter(void 0, void 0, void 0, function* () {
111
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
112
+ calling.initialize();
113
+ initializeSpies();
114
+ yield calling.startOutgoingDirectCall(user2_id);
115
+ yield (0, Utils_1.sleep)(1000);
116
+ // An offer and at least one ICE message should have been sent.
117
+ (0, chai_1.expect)(handleOutgoingSignalingSpy.callCount).to.be.gt(1);
118
+ yield (0, Utils_1.sleep)(2000);
119
+ // Cleanup.
120
+ const handleStateChangedSpy = sinon_1.default.spy(index_1.RingRTC.call, 'handleStateChanged');
121
+ (0, chai_1.expect)(calling.hangup()).to.be.true;
122
+ yield (0, Utils_1.sleep)(500);
123
+ handleStateChangedSpy.should.have.been.calledOnce;
124
+ (0, chai_1.expect)(calling.hangup()).to.be.false;
125
+ yield (0, Utils_1.sleep)(100);
126
+ }));
127
+ it('can establish incoming call', () => __awaiter(void 0, void 0, void 0, function* () {
128
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
129
+ calling.initialize();
130
+ initializeSpies();
131
+ // Generate incoming calling message
132
+ const message_age_sec = 1;
133
+ const message_received_at_counter = 10;
134
+ const callId = new long_1.default(1, 1, true);
135
+ const offerCallingMessage = generateOfferCallingMessage(callId);
136
+ index_1.RingRTC.handleCallingMessage(user2_id, Buffer.from((0, Utils_1.uuidToBytes)(user2_id)), user2_device_id, user1_device_id, message_age_sec, message_received_at_counter, offerCallingMessage, user2_identity_key, user1_identity_key);
137
+ yield (0, Utils_1.sleep)(1000);
138
+ handleIncomingCallSpy.should.have.been.calledOnce;
139
+ chai_1.assert.equal(index_1.CallState.Prering, index_1.RingRTC.call.state);
140
+ // Hangup call
141
+ (0, chai_1.expect)(calling.hangup()).to.be.true;
142
+ yield (0, Utils_1.sleep)(500);
143
+ // Validate hangup related callbacks and call state
144
+ handleAutoEndedIncomingCallRequestSpy.should.have.been.calledOnce;
145
+ (0, chai_1.expect)(handleOutgoingSignalingSpy.callCount).to.be.gt(1);
146
+ chai_1.assert.equal(index_1.CallState.Ended, index_1.RingRTC.call.state);
147
+ }));
148
+ it('outgoing call wins glare when incoming call id is lower', () => __awaiter(void 0, void 0, void 0, function* () {
149
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
150
+ calling.initialize();
151
+ initializeSpies();
152
+ yield runGlareScenario(calling, true, 0, 0);
153
+ }));
154
+ it('outgoing call wins glare when incoming call id is lower even when outgoing call settings are delayed', () => __awaiter(void 0, void 0, void 0, function* () {
155
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
156
+ calling.initialize();
157
+ initializeSpies();
158
+ yield runGlareScenario(calling, true, 0, 1000);
159
+ }));
160
+ it('outgoing call loses glare when incoming call id is higher even when outgoing call settings are delayed', () => __awaiter(void 0, void 0, void 0, function* () {
161
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
162
+ calling.initialize();
163
+ initializeSpies();
164
+ yield runGlareScenario(calling, false, 0, 1000);
165
+ }));
166
+ it('outgoing call loses glare when incoming call id is higher', () => __awaiter(void 0, void 0, void 0, function* () {
167
+ let calling = new CallingClass_1.CallingClass(user1_name, user1_id);
168
+ calling.initialize();
169
+ initializeSpies();
170
+ yield runGlareScenario(calling, false, 0, 0);
171
+ }));
172
+ function runGlareScenario(calling, outgoingWinner, delayIncomingCallSetings, delayOutgoingCallSetings) {
173
+ return __awaiter(this, void 0, void 0, function* () {
174
+ calling.delayOutgoingCallSettingsRequest = delayOutgoingCallSetings;
175
+ calling.delayIncomingCallSettingsRequest = delayIncomingCallSetings;
176
+ const outgoingCallLatch = (0, Utils_1.countDownLatch)(1);
177
+ calling
178
+ .startOutgoingDirectCall(user2_id)
179
+ .then(result => {
180
+ (0, Utils_1.log)('Outgoing call succeeded as expected');
181
+ outgoingCallLatch.countDown();
182
+ })
183
+ .catch(e => {
184
+ chai_1.assert.fail('Outgoing call should not have failed');
185
+ });
186
+ yield outgoingCallLatch.finished;
187
+ const outgoingCallId = long_1.default.fromValue(index_1.RingRTC.call.callId);
188
+ // Generate a call id based on the desired glare winner
189
+ const incomingCallId = outgoingCallId.unsigned
190
+ ? new long_1.default(outgoingWinner ? outgoingCallId.low - 1 : outgoingCallId.low + 1, outgoingCallId.high, outgoingCallId.unsigned)
191
+ : new long_1.default(outgoingWinner ? outgoingCallId.low + 1 : outgoingCallId.low - 1, outgoingCallId.high, outgoingCallId.unsigned);
192
+ // Generate incoming calling message
193
+ const message_age_sec = 1;
194
+ const message_received_at_counter = 10;
195
+ const offerCallingMessage = generateOfferCallingMessage(incomingCallId);
196
+ // Initiate an incoming call
197
+ index_1.RingRTC.handleCallingMessage(user2_id, Buffer.from((0, Utils_1.uuidToBytes)(user2_id)), user2_device_id, user1_device_id, message_age_sec, message_received_at_counter, offerCallingMessage, user2_identity_key, user1_identity_key);
198
+ yield (0, Utils_1.sleep)(1000);
199
+ if (outgoingWinner) {
200
+ chai_1.assert.isTrue(outgoingCallId.eq(long_1.default.fromValue(index_1.RingRTC.call.callId)));
201
+ }
202
+ else {
203
+ chai_1.assert.isTrue(incomingCallId.eq(long_1.default.fromValue(index_1.RingRTC.call.callId)));
204
+ }
205
+ // Cleanup.
206
+ (0, chai_1.expect)(calling.hangup()).to.be.true;
207
+ yield (0, Utils_1.sleep)(500);
208
+ chai_1.assert.equal(index_1.CallState.Ended, index_1.RingRTC.call.state);
209
+ });
210
+ }
71
211
  });
@@ -0,0 +1,7 @@
1
+ export declare function countDownLatch(count: number): {
2
+ countDown: () => void;
3
+ finished: Promise<unknown>;
4
+ };
5
+ export declare function log(line: string): void;
6
+ export declare let sleep: (timeout: number) => Promise<void>;
7
+ export declare function uuidToBytes(uuid: string): Uint8Array;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2023 Signal Messenger, LLC
4
+ // SPDX-License-Identifier: AGPL-3.0-only
5
+ //
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.uuidToBytes = exports.sleep = exports.log = exports.countDownLatch = void 0;
8
+ const lodash_1 = require("lodash");
9
+ const chai_1 = require("chai");
10
+ function countDownLatch(count) {
11
+ (0, chai_1.assert)(count > 0, 'count must be a positive number');
12
+ let resolve;
13
+ const finished = new Promise(resolveInternal => {
14
+ resolve = resolveInternal;
15
+ });
16
+ const countDown = () => {
17
+ count--;
18
+ if (count == 0) {
19
+ resolve();
20
+ }
21
+ };
22
+ return {
23
+ countDown: countDown,
24
+ finished,
25
+ };
26
+ }
27
+ exports.countDownLatch = countDownLatch;
28
+ function log(line) {
29
+ // Standard logging used for checkpoints.
30
+ // Use --renderer to see the log output. (edit: Maybe always shown now?)
31
+ // BgYellow
32
+ console.log(`\x1b[43m${line}\x1b[0m`);
33
+ }
34
+ exports.log = log;
35
+ let sleep = (timeout) => {
36
+ return new Promise(resolve => {
37
+ setTimeout(() => {
38
+ // BgBlue
39
+ console.log(`\x1b[44msleeping ${timeout} ms\x1b[0m`);
40
+ resolve();
41
+ }, timeout);
42
+ });
43
+ };
44
+ exports.sleep = sleep;
45
+ function uuidToBytes(uuid) {
46
+ if (uuid.length !== 36) {
47
+ return new Uint8Array(0);
48
+ }
49
+ return Uint8Array.from((0, lodash_1.chunk)(uuid.replace(/-/g, ''), 2).map(pair => parseInt(pair.join(''), 16)));
50
+ }
51
+ exports.uuidToBytes = uuidToBytes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalapp/ringrtc",
3
- "version": "2.23.1",
3
+ "version": "2.24.0",
4
4
  "description": "Signal Messenger voice and video calling library.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "clean": "rimraf dist",
14
- "test": "electron-mocha --recursive dist/test",
14
+ "test": "electron-mocha --recursive dist/test --timeout 10000",
15
15
  "eslint": "eslint --cache .",
16
16
  "lint": "yarn format --list-different && yarn eslint",
17
17
  "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "config": {
22
22
  "prebuildUrl": "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v${npm_package_version}.tar.gz",
23
- "prebuildChecksum": "11df4ca9c2fd61daabc9fb8b12f8dd887c5ec3b6aa3937701a9d810345a0da9a"
23
+ "prebuildChecksum": "7f7f7a795d4387d59e5262c6a3b8efb910f2d3f9c60a18316e49bb9a29d65343"
24
24
  },
25
25
  "author": "",
26
26
  "license": "AGPL-3.0-only",
@@ -31,10 +31,12 @@
31
31
  "@types/chai": "4.2.18",
32
32
  "@types/chai-as-promised": "^7.1.3",
33
33
  "@types/dom-mediacapture-transform": "0.1.2",
34
+ "@types/long": "4.0.1",
34
35
  "@types/mocha": "5.2.7",
35
36
  "@types/node": "14.14.37",
36
37
  "@types/offscreencanvas": "^2019.6.4",
37
38
  "@types/react": "16.8.5",
39
+ "@types/sinon-chai": "^3.2.9",
38
40
  "chai": "4.3.5",
39
41
  "chai-as-promised": "^7.1.1",
40
42
  "electron": "15.3.2",
@@ -46,9 +48,12 @@
46
48
  "eslint-plugin-mocha": "9.0.0",
47
49
  "eslint-plugin-more": "1.0.5",
48
50
  "eslint-plugin-react": "7.28.0",
51
+ "long": "4.0.0",
49
52
  "mocha": "9.2.0",
50
53
  "prettier": "^2.5.1",
51
54
  "rimraf": "3.0.2",
55
+ "sinon": "^15.0.1",
56
+ "sinon-chai": "^3.7.0",
52
57
  "typescript": "4.5.2",
53
58
  "yarn-audit-fix": "^9.2.2"
54
59
  }