@signalapp/ringrtc 2.23.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.
@@ -0,0 +1,66 @@
1
+ /// <reference types="node" />
2
+ interface Ref<T> {
3
+ readonly current: T | null;
4
+ }
5
+ export declare enum VideoPixelFormatEnum {
6
+ I420 = 0,
7
+ Nv12 = 1,
8
+ Rgba = 2
9
+ }
10
+ export interface VideoFrameSource {
11
+ receiveVideoFrame(buffer: Buffer): [number, number] | undefined;
12
+ }
13
+ interface VideoFrameSender {
14
+ sendVideoFrame(width: number, height: number, format: VideoPixelFormatEnum, buffer: Buffer): void;
15
+ }
16
+ export declare class GumVideoCaptureOptions {
17
+ maxWidth: number;
18
+ maxHeight: number;
19
+ maxFramerate: number;
20
+ preferredDeviceId?: string;
21
+ screenShareSourceId?: string;
22
+ }
23
+ export declare class GumVideoCapturer {
24
+ private defaultCaptureOptions;
25
+ private localPreview?;
26
+ private captureOptions?;
27
+ private sender?;
28
+ private mediaStream?;
29
+ private spawnedSenderRunning;
30
+ private preferredDeviceId?;
31
+ private updateLocalPreviewIntervalId?;
32
+ constructor(defaultCaptureOptions: GumVideoCaptureOptions);
33
+ capturing(): boolean;
34
+ setLocalPreview(localPreview: Ref<HTMLVideoElement> | undefined): void;
35
+ enableCapture(): void;
36
+ enableCaptureAndSend(sender: VideoFrameSender, options?: GumVideoCaptureOptions): void;
37
+ disable(): void;
38
+ setPreferredDevice(deviceId: string): Promise<void>;
39
+ enumerateDevices(): Promise<MediaDeviceInfo[]>;
40
+ private getUserMedia;
41
+ private startCapturing;
42
+ private stopCapturing;
43
+ private startSending;
44
+ private spawnSender;
45
+ private stopSending;
46
+ private updateLocalPreviewSourceObject;
47
+ }
48
+ export declare const MAX_VIDEO_CAPTURE_WIDTH: number;
49
+ export declare const MAX_VIDEO_CAPTURE_HEIGHT: number;
50
+ export declare const MAX_VIDEO_CAPTURE_AREA: number;
51
+ export declare const MAX_VIDEO_CAPTURE_BUFFER_SIZE: number;
52
+ export declare class CanvasVideoRenderer {
53
+ private canvas?;
54
+ private buffer;
55
+ private imageData?;
56
+ private source?;
57
+ private rafId?;
58
+ constructor();
59
+ setCanvas(canvas: Ref<HTMLCanvasElement> | undefined): void;
60
+ enable(source: VideoFrameSource): void;
61
+ disable(): void;
62
+ private requestAnimationFrameCallback;
63
+ private renderBlack;
64
+ private renderVideoFrame;
65
+ }
66
+ export {};
@@ -0,0 +1,421 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2019-2021 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
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.CanvasVideoRenderer = exports.MAX_VIDEO_CAPTURE_BUFFER_SIZE = exports.MAX_VIDEO_CAPTURE_AREA = exports.MAX_VIDEO_CAPTURE_HEIGHT = exports.MAX_VIDEO_CAPTURE_WIDTH = exports.GumVideoCapturer = exports.GumVideoCaptureOptions = exports.VideoPixelFormatEnum = void 0;
17
+ const index_1 = require("../index");
18
+ // Given a weird name to not conflict with WebCodec's VideoPixelFormat
19
+ var VideoPixelFormatEnum;
20
+ (function (VideoPixelFormatEnum) {
21
+ VideoPixelFormatEnum[VideoPixelFormatEnum["I420"] = 0] = "I420";
22
+ VideoPixelFormatEnum[VideoPixelFormatEnum["Nv12"] = 1] = "Nv12";
23
+ VideoPixelFormatEnum[VideoPixelFormatEnum["Rgba"] = 2] = "Rgba";
24
+ })(VideoPixelFormatEnum = exports.VideoPixelFormatEnum || (exports.VideoPixelFormatEnum = {}));
25
+ function videoPixelFormatFromEnum(format) {
26
+ switch (format) {
27
+ case VideoPixelFormatEnum.I420: {
28
+ return 'I420';
29
+ }
30
+ case VideoPixelFormatEnum.Nv12: {
31
+ return 'NV12';
32
+ }
33
+ case VideoPixelFormatEnum.Rgba: {
34
+ return 'RGBA';
35
+ }
36
+ }
37
+ }
38
+ function videoPixelFormatToEnum(format) {
39
+ switch (format) {
40
+ case 'I420': {
41
+ return VideoPixelFormatEnum.I420;
42
+ }
43
+ case 'NV12': {
44
+ return VideoPixelFormatEnum.Nv12;
45
+ }
46
+ case 'RGBA': {
47
+ return VideoPixelFormatEnum.Rgba;
48
+ }
49
+ }
50
+ }
51
+ class GumVideoCaptureOptions {
52
+ constructor() {
53
+ this.maxWidth = 640;
54
+ this.maxHeight = 480;
55
+ this.maxFramerate = 30;
56
+ }
57
+ }
58
+ exports.GumVideoCaptureOptions = GumVideoCaptureOptions;
59
+ class GumVideoCapturer {
60
+ constructor(defaultCaptureOptions) {
61
+ this.spawnedSenderRunning = false;
62
+ this.defaultCaptureOptions = defaultCaptureOptions;
63
+ }
64
+ capturing() {
65
+ return this.captureOptions != undefined;
66
+ }
67
+ setLocalPreview(localPreview) {
68
+ var _a;
69
+ const oldLocalPreview = (_a = this.localPreview) === null || _a === void 0 ? void 0 : _a.current;
70
+ if (oldLocalPreview) {
71
+ oldLocalPreview.srcObject = null;
72
+ }
73
+ this.localPreview = localPreview;
74
+ this.updateLocalPreviewSourceObject();
75
+ // This is a dumb hack around the fact that sometimes the
76
+ // this.localPreview.current is updated without a call
77
+ // to setLocalPreview, in which case the local preview
78
+ // won't be rendered.
79
+ if (this.updateLocalPreviewIntervalId != undefined) {
80
+ clearInterval(this.updateLocalPreviewIntervalId);
81
+ }
82
+ this.updateLocalPreviewIntervalId = setInterval(this.updateLocalPreviewSourceObject.bind(this), 1000);
83
+ }
84
+ enableCapture() {
85
+ // tslint:disable no-floating-promises
86
+ this.startCapturing(this.defaultCaptureOptions);
87
+ }
88
+ enableCaptureAndSend(sender, options) {
89
+ // tslint:disable no-floating-promises
90
+ this.startCapturing(options !== null && options !== void 0 ? options : this.defaultCaptureOptions);
91
+ this.startSending(sender);
92
+ }
93
+ disable() {
94
+ this.stopCapturing();
95
+ this.stopSending();
96
+ if (this.updateLocalPreviewIntervalId != undefined) {
97
+ clearInterval(this.updateLocalPreviewIntervalId);
98
+ }
99
+ this.updateLocalPreviewIntervalId = undefined;
100
+ }
101
+ setPreferredDevice(deviceId) {
102
+ return __awaiter(this, void 0, void 0, function* () {
103
+ this.preferredDeviceId = deviceId;
104
+ if (this.captureOptions) {
105
+ const captureOptions = this.captureOptions;
106
+ const sender = this.sender;
107
+ this.disable();
108
+ this.startCapturing(captureOptions);
109
+ if (sender) {
110
+ this.startSending(sender);
111
+ }
112
+ }
113
+ });
114
+ }
115
+ enumerateDevices() {
116
+ return __awaiter(this, void 0, void 0, function* () {
117
+ const devices = yield window.navigator.mediaDevices.enumerateDevices();
118
+ const cameras = devices.filter(d => d.kind == 'videoinput');
119
+ return cameras;
120
+ });
121
+ }
122
+ getUserMedia(options) {
123
+ var _a;
124
+ // TODO: Figure out a better way to make typescript accept "mandatory".
125
+ let constraints = {
126
+ audio: false,
127
+ video: {
128
+ deviceId: (_a = options.preferredDeviceId) !== null && _a !== void 0 ? _a : this.preferredDeviceId,
129
+ width: {
130
+ max: options.maxWidth,
131
+ },
132
+ height: {
133
+ max: options.maxHeight,
134
+ },
135
+ frameRate: {
136
+ max: options.maxFramerate,
137
+ },
138
+ },
139
+ };
140
+ if (options.screenShareSourceId != undefined) {
141
+ constraints.video = {
142
+ mandatory: {
143
+ chromeMediaSource: 'desktop',
144
+ chromeMediaSourceId: options.screenShareSourceId,
145
+ maxWidth: options.maxWidth,
146
+ maxHeight: options.maxHeight,
147
+ maxFrameRate: options.maxFramerate,
148
+ },
149
+ };
150
+ }
151
+ return window.navigator.mediaDevices.getUserMedia(constraints);
152
+ }
153
+ startCapturing(options) {
154
+ return __awaiter(this, void 0, void 0, function* () {
155
+ if (this.capturing()) {
156
+ index_1.RingRTC.logWarn('startCapturing(): already capturing');
157
+ return;
158
+ }
159
+ index_1.RingRTC.logInfo(`startCapturing(): ${options.maxWidth}x${options.maxHeight}@${options.maxFramerate}`);
160
+ this.captureOptions = options;
161
+ try {
162
+ // If we start/stop/start, we may have concurrent calls to getUserMedia,
163
+ // which is what we want if we're switching from camera to screenshare.
164
+ // But we need to make sure we deal with the fact that things might be
165
+ // different after the await here.
166
+ const mediaStream = yield this.getUserMedia(options);
167
+ // It's possible video was disabled, switched to screenshare, or
168
+ // switched to a different camera while awaiting a response, in
169
+ // which case we need to disable the camera we just accessed.
170
+ if (this.captureOptions != options) {
171
+ index_1.RingRTC.logWarn('startCapturing(): different state after getUserMedia()');
172
+ for (const track of mediaStream.getVideoTracks()) {
173
+ // Make the light turn off faster
174
+ track.stop();
175
+ }
176
+ return;
177
+ }
178
+ this.mediaStream = mediaStream;
179
+ if (!this.spawnedSenderRunning &&
180
+ this.mediaStream != undefined &&
181
+ this.sender != undefined) {
182
+ this.spawnSender(this.mediaStream, this.sender);
183
+ }
184
+ this.updateLocalPreviewSourceObject();
185
+ }
186
+ catch (e) {
187
+ index_1.RingRTC.logError(`startCapturing(): ${e}`);
188
+ // It's possible video was disabled, switched to screenshare, or
189
+ // switched to a different camera while awaiting a response, in
190
+ // which case we should reset the captureOptions if we set them.
191
+ if (this.captureOptions == options) {
192
+ // We couldn't open the camera. Oh well.
193
+ this.captureOptions = undefined;
194
+ }
195
+ }
196
+ });
197
+ }
198
+ stopCapturing() {
199
+ if (!this.capturing()) {
200
+ index_1.RingRTC.logWarn('stopCapturing(): not capturing');
201
+ return;
202
+ }
203
+ index_1.RingRTC.logInfo('stopCapturing()');
204
+ this.captureOptions = undefined;
205
+ if (!!this.mediaStream) {
206
+ for (const track of this.mediaStream.getVideoTracks()) {
207
+ // Make the light turn off faster
208
+ track.stop();
209
+ }
210
+ this.mediaStream = undefined;
211
+ }
212
+ this.updateLocalPreviewSourceObject();
213
+ }
214
+ startSending(sender) {
215
+ if (this.sender === sender) {
216
+ return;
217
+ }
218
+ if (!!this.sender) {
219
+ // If we're replacing an existing sender, make sure we stop the
220
+ // current setInterval loop before starting another one.
221
+ this.stopSending();
222
+ }
223
+ this.sender = sender;
224
+ if (!this.spawnedSenderRunning && this.mediaStream != undefined) {
225
+ this.spawnSender(this.mediaStream, this.sender);
226
+ }
227
+ }
228
+ spawnSender(mediaStream, sender) {
229
+ const track = mediaStream.getVideoTracks()[0];
230
+ if (track == undefined || this.spawnedSenderRunning) {
231
+ return;
232
+ }
233
+ if (track.readyState === "ended") {
234
+ this.stopCapturing();
235
+ index_1.RingRTC.logError(`spawnSender(): Video track ended before spawning sender`);
236
+ return;
237
+ }
238
+ const reader = new MediaStreamTrackProcessor({
239
+ track,
240
+ }).readable.getReader();
241
+ const buffer = Buffer.alloc(exports.MAX_VIDEO_CAPTURE_BUFFER_SIZE);
242
+ this.spawnedSenderRunning = true;
243
+ (() => __awaiter(this, void 0, void 0, function* () {
244
+ var _a;
245
+ try {
246
+ while (sender === this.sender && mediaStream == this.mediaStream) {
247
+ const { done, value: frame } = yield reader.read();
248
+ if (done) {
249
+ break;
250
+ }
251
+ if (!frame) {
252
+ continue;
253
+ }
254
+ try {
255
+ const format = videoPixelFormatToEnum((_a = frame.format) !== null && _a !== void 0 ? _a : 'I420');
256
+ if (format == undefined) {
257
+ index_1.RingRTC.logWarn(`Unsupported video frame format: ${frame.format}`);
258
+ break;
259
+ }
260
+ frame.copyTo(buffer);
261
+ sender.sendVideoFrame(frame.codedWidth, frame.codedHeight, format, buffer);
262
+ }
263
+ catch (e) {
264
+ index_1.RingRTC.logError(`sendVideoFrame(): ${e}`);
265
+ }
266
+ finally {
267
+ // This must be called for more frames to come.
268
+ frame.close();
269
+ }
270
+ }
271
+ }
272
+ catch (e) {
273
+ index_1.RingRTC.logError(`spawnSender(): ${e}`);
274
+ }
275
+ finally {
276
+ reader.releaseLock();
277
+ }
278
+ this.spawnedSenderRunning = false;
279
+ }))();
280
+ }
281
+ stopSending() {
282
+ // The spawned sender should stop
283
+ this.sender = undefined;
284
+ }
285
+ updateLocalPreviewSourceObject() {
286
+ if (!this.localPreview) {
287
+ return;
288
+ }
289
+ const localPreview = this.localPreview.current;
290
+ if (!localPreview) {
291
+ return;
292
+ }
293
+ const { mediaStream = null } = this;
294
+ if (localPreview.srcObject === mediaStream) {
295
+ return;
296
+ }
297
+ if (mediaStream) {
298
+ localPreview.srcObject = mediaStream;
299
+ if (localPreview.width === 0) {
300
+ localPreview.width = this.captureOptions.maxWidth;
301
+ }
302
+ if (localPreview.height === 0) {
303
+ localPreview.height = this.captureOptions.maxHeight;
304
+ }
305
+ }
306
+ else {
307
+ localPreview.srcObject = null;
308
+ }
309
+ }
310
+ }
311
+ exports.GumVideoCapturer = GumVideoCapturer;
312
+ // We add 10% in each dimension to allow for things that are slightly wider or taller than 1080p.
313
+ const MAX_VIDEO_CAPTURE_MULTIPLIER = 1.0;
314
+ exports.MAX_VIDEO_CAPTURE_WIDTH = 1920 * MAX_VIDEO_CAPTURE_MULTIPLIER;
315
+ exports.MAX_VIDEO_CAPTURE_HEIGHT = 1080 * MAX_VIDEO_CAPTURE_MULTIPLIER;
316
+ exports.MAX_VIDEO_CAPTURE_AREA = exports.MAX_VIDEO_CAPTURE_WIDTH * exports.MAX_VIDEO_CAPTURE_HEIGHT;
317
+ exports.MAX_VIDEO_CAPTURE_BUFFER_SIZE = exports.MAX_VIDEO_CAPTURE_AREA * 4;
318
+ class CanvasVideoRenderer {
319
+ constructor() {
320
+ this.buffer = Buffer.alloc(exports.MAX_VIDEO_CAPTURE_BUFFER_SIZE);
321
+ }
322
+ setCanvas(canvas) {
323
+ this.canvas = canvas;
324
+ }
325
+ enable(source) {
326
+ if (this.source === source) {
327
+ return;
328
+ }
329
+ if (!!this.source) {
330
+ // If we're replacing an existing source, make sure we stop the
331
+ // current rAF loop before starting another one.
332
+ if (this.rafId) {
333
+ window.cancelAnimationFrame(this.rafId);
334
+ }
335
+ }
336
+ this.source = source;
337
+ this.requestAnimationFrameCallback();
338
+ }
339
+ disable() {
340
+ this.renderBlack();
341
+ this.source = undefined;
342
+ if (this.rafId) {
343
+ window.cancelAnimationFrame(this.rafId);
344
+ }
345
+ }
346
+ requestAnimationFrameCallback() {
347
+ this.renderVideoFrame();
348
+ this.rafId = window.requestAnimationFrame(this.requestAnimationFrameCallback.bind(this));
349
+ }
350
+ renderBlack() {
351
+ if (!this.canvas) {
352
+ return;
353
+ }
354
+ const canvas = this.canvas.current;
355
+ if (!canvas) {
356
+ return;
357
+ }
358
+ const context = canvas.getContext('2d');
359
+ if (!context) {
360
+ return;
361
+ }
362
+ context.fillStyle = 'black';
363
+ context.fillRect(0, 0, canvas.width, canvas.height);
364
+ }
365
+ renderVideoFrame() {
366
+ var _a, _b;
367
+ if (!this.source || !this.canvas) {
368
+ return;
369
+ }
370
+ const canvas = this.canvas.current;
371
+ if (!canvas) {
372
+ return;
373
+ }
374
+ const context = canvas.getContext('2d');
375
+ if (!context) {
376
+ return;
377
+ }
378
+ const frame = this.source.receiveVideoFrame(this.buffer);
379
+ if (!frame) {
380
+ return;
381
+ }
382
+ const [width, height] = frame;
383
+ if (canvas.clientWidth <= 0 ||
384
+ width <= 0 ||
385
+ canvas.clientHeight <= 0 ||
386
+ height <= 0) {
387
+ return;
388
+ }
389
+ const frameAspectRatio = width / height;
390
+ const canvasAspectRatio = canvas.clientWidth / canvas.clientHeight;
391
+ let dx = 0;
392
+ let dy = 0;
393
+ if (frameAspectRatio > canvasAspectRatio) {
394
+ // Frame wider than view: We need bars at the top and bottom
395
+ canvas.width = width;
396
+ canvas.height = width / canvasAspectRatio;
397
+ dy = (canvas.height - height) / 2;
398
+ }
399
+ else if (frameAspectRatio < canvasAspectRatio) {
400
+ // Frame narrower than view: We need pillars on the sides
401
+ canvas.width = height * canvasAspectRatio;
402
+ canvas.height = height;
403
+ dx = (canvas.width - width) / 2;
404
+ }
405
+ else {
406
+ // Will stretch perfectly with no bars
407
+ canvas.width = width;
408
+ canvas.height = height;
409
+ }
410
+ if (dx > 0 || dy > 0) {
411
+ context.fillStyle = 'black';
412
+ context.fillRect(0, 0, canvas.width, canvas.height);
413
+ }
414
+ if (((_a = this.imageData) === null || _a === void 0 ? void 0 : _a.width) !== width || ((_b = this.imageData) === null || _b === void 0 ? void 0 : _b.height) !== height) {
415
+ this.imageData = new ImageData(width, height);
416
+ }
417
+ this.imageData.data.set(this.buffer.subarray(0, width * height * 4));
418
+ context.putImageData(this.imageData, dx, dy);
419
+ }
420
+ }
421
+ exports.CanvasVideoRenderer = CanvasVideoRenderer;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ //
3
+ // Copyright 2019-2021 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
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ const chai_1 = require("chai");
17
+ const chaiAsPromised = require("chai-as-promised");
18
+ const index_1 = require("../index");
19
+ (0, chai_1.use)(chaiAsPromised);
20
+ describe('RingRTC', () => {
21
+ it('testsInitialization', () => {
22
+ chai_1.assert.isNotNull(index_1.RingRTC, "RingRTC didn't initialize!");
23
+ });
24
+ it('reports an age for expired offers', () => __awaiter(void 0, void 0, void 0, function* () {
25
+ const offer = {
26
+ offer: {
27
+ callId: { high: 0, low: 123 },
28
+ type: index_1.OfferType.AudioCall,
29
+ opaque: Buffer.from([]),
30
+ },
31
+ supportsMultiRing: true,
32
+ };
33
+ const age = 60 * 60;
34
+ try {
35
+ const { reason, ageSec: reportedAge } = yield new Promise((resolve, _reject) => {
36
+ index_1.RingRTC.handleAutoEndedIncomingCallRequest = (_callId, _remoteUserId, reason, ageSec) => {
37
+ resolve({ reason, ageSec });
38
+ };
39
+ index_1.RingRTC.handleCallingMessage('remote', null, 4, 2, age, 1, offer, Buffer.from([]), Buffer.from([]));
40
+ });
41
+ chai_1.assert.equal(reason, index_1.CallEndedReason.ReceivedOfferExpired);
42
+ chai_1.assert.equal(reportedAge, age);
43
+ }
44
+ finally {
45
+ index_1.RingRTC.handleAutoEndedIncomingCallRequest = null;
46
+ }
47
+ }));
48
+ it('reports 0 as the age of other auto-ended offers', () => __awaiter(void 0, void 0, void 0, function* () {
49
+ const offer = {
50
+ offer: {
51
+ callId: { high: 0, low: 123 },
52
+ type: index_1.OfferType.AudioCall,
53
+ opaque: Buffer.from([]),
54
+ },
55
+ supportsMultiRing: true,
56
+ };
57
+ try {
58
+ const { reason, ageSec: reportedAge } = yield new Promise((resolve, _reject) => {
59
+ index_1.RingRTC.handleAutoEndedIncomingCallRequest = (_callId, _remoteUserId, reason, ageSec) => {
60
+ resolve({ reason, ageSec });
61
+ };
62
+ index_1.RingRTC.handleCallingMessage('remote', null, 4, 2, 10, 2, offer, Buffer.from([]), Buffer.from([]));
63
+ });
64
+ chai_1.assert.equal(reason, index_1.CallEndedReason.Declined); // because we didn't set handleIncomingCall.
65
+ chai_1.assert.equal(reportedAge, 0);
66
+ }
67
+ finally {
68
+ index_1.RingRTC.handleAutoEndedIncomingCallRequest = null;
69
+ }
70
+ }));
71
+ });
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@signalapp/ringrtc",
3
+ "version": "2.23.0",
4
+ "description": "Signal Messenger voice and video calling library.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/*",
9
+ "scripts/fetch-prebuild.js"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "clean": "rimraf dist",
14
+ "test": "electron-mocha --recursive dist/test",
15
+ "eslint": "eslint --cache .",
16
+ "lint": "yarn format --list-different && yarn eslint",
17
+ "format": "prettier --write \"*.{css,js,json,md,scss,ts,tsx}\" \"./**/*.{css,js,json,md,scss,ts,tsx}\"",
18
+ "install": "node scripts/fetch-prebuild.js",
19
+ "prepublishOnly": "node scripts/prepublish.js"
20
+ },
21
+ "config": {
22
+ "prebuildUrl": "https://build-artifacts.signal.org/libraries/ringrtc-desktop-build-v${npm_package_version}.tar.gz",
23
+ "prebuildChecksum": "92f2a82a2acc5b853855a17b57f31354166783ec938f2452563f07e32e263c78"
24
+ },
25
+ "author": "",
26
+ "license": "AGPL-3.0-only",
27
+ "dependencies": {
28
+ "tar": "^6.1.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/chai": "4.2.18",
32
+ "@types/chai-as-promised": "^7.1.3",
33
+ "@types/dom-mediacapture-transform": "0.1.2",
34
+ "@types/mocha": "5.2.7",
35
+ "@types/node": "14.14.37",
36
+ "@types/offscreencanvas": "^2019.6.4",
37
+ "@types/react": "16.8.5",
38
+ "chai": "4.3.5",
39
+ "chai-as-promised": "^7.1.1",
40
+ "electron": "15.3.2",
41
+ "electron-mocha": "11.0.2",
42
+ "eslint": "7.32.0",
43
+ "eslint-config-airbnb-typescript-prettier": "4.2.0",
44
+ "eslint-config-prettier": "6.15.0",
45
+ "eslint-plugin-import": "2.25.4",
46
+ "eslint-plugin-mocha": "9.0.0",
47
+ "eslint-plugin-more": "1.0.5",
48
+ "eslint-plugin-react": "7.28.0",
49
+ "mocha": "9.2.0",
50
+ "prettier": "^2.5.1",
51
+ "rimraf": "3.0.2",
52
+ "typescript": "4.5.2",
53
+ "yarn-audit-fix": "^9.2.2"
54
+ }
55
+ }