Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ export function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
if (!window.RTCPeerConnection) {
return;
}
const addEventListener = Object.getOwnPropertyDescriptor(
EventTarget.prototype, 'addEventListener');
if (!addEventListener.writable) {

const proto = window.RTCPeerConnection.prototype;

const isAddEventListenerWritable =
tryMakePropertyWritable(proto, 'addEventListener');
const isRemoveEventListenerWritable =
tryMakePropertyWritable(proto, 'removeEventListener');
const canPolyfill =
isAddEventListenerWritable && isRemoveEventListenerWritable;

if (!canPolyfill) {
log('Unable to polyfill events');
return;
}
const proto = window.RTCPeerConnection.prototype;
const nativeAddEventListener = proto.addEventListener;

proto.addEventListener = function(nativeEventName, cb) {
Expand Down Expand Up @@ -104,6 +111,28 @@ export function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
});
}

/**
* Tries to ensure the property is writable.
* If the property is read-only but configurable,
* it will be redefined to be writable.
* @param {object} target the object to check the property on.
* @param {string} property the property to check.
* @return {boolean} true if the property is writable, false otherwise.
*/
export function tryMakePropertyWritable(target, property) {
try {
Object.defineProperty(target, property, {
value: target[property],
writable: true,
configurable: true,
});

return true;
} catch (e) {
return false;
}
}

export function disableLog(bool) {
if (typeof bool !== 'boolean') {
return new Error('Argument type: ' + typeof bool +
Expand Down
69 changes: 69 additions & 0 deletions test/unit/wrapPeerConnectionEvent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/

describe('wrapPeerConnectionEvent', () => {
const wrapPeerConnectionEvent =
require('../../dist/utils.js').wrapPeerConnectionEvent;
let window;
let RTCPeerConnection;
let addEventListener;
let removeEventListener;

beforeEach(() => {
addEventListener = jest.fn();
removeEventListener = jest.fn();
RTCPeerConnection = jest.fn();

window = {
RTCPeerConnection,
};
});

it('should patch RTCPeerConnection if the props are writable', () => {
changeAccessorDescriptor({writable: false, configurable: true});

wrapPeerConnectionEvent(window, 'track', (e) => {
return {type: e.type, wrapped: true};
});

expect(RTCPeerConnection.prototype.addEventListener)
.not.toBe(addEventListener);
expect(RTCPeerConnection.prototype.removeEventListener)
.not.toBe(removeEventListener);
});

it('shouldn\'t patch RTCPeerConnection if the props are not configurable',
() => {
changeAccessorDescriptor({writable: false, configurable: false});

wrapPeerConnectionEvent(window, 'track', () => {
return null;
});

expect(RTCPeerConnection.prototype.addEventListener)
.toBe(addEventListener);
expect(RTCPeerConnection.prototype.removeEventListener)
.toBe(removeEventListener);
});

function changeAccessorDescriptor(
{writable, configurable},
) {
Object.defineProperty(RTCPeerConnection.prototype, 'addEventListener', {
value: addEventListener,
writable,
configurable,
});
Object.defineProperty(RTCPeerConnection.prototype, 'removeEventListener', {
value: removeEventListener,
writable,
configurable,
});
}
});

Loading