Skip to content

Commit 2a01b7a

Browse files
imaTik0Paweł Aniszewski
andauthored
refactor: webstreams overhaul (#171)
* refactor(webstreams): remove dead code and add WebRTC type extensions - Logger: drop LogLevel enum and setLevel/getLevel; use plain number constant - Queue: remove 7 unused methods (dequeue, peek, size, isEmpty, clear, toArray, Symbol.iterator); replace boolean processing flag with drainPromise; replace Math.random() IDs with sequential counter; drain() now re-throws on error - WebRtcExtensions.ts: add typed interfaces for insertable streams API (EncodedStreams, RTCRtpScriptTransformOptions with optional kind, sender/ receiver with transform, RTCConfigurationWithInsertableStreams, WindowWithRTCRtpScriptTransform, WindowWithWasmHandler) * refactor(webstreams): fix layer boundaries and extract EncryptTransform Move service/ types into their owning layer: - src/service/EventDispatcher.ts → src/webStreams/EventDispatcher.ts (fix removeOnStateChangeListener filter, add JSDoc, wrap emit in try/catch) - src/service/WebRtcInterface.ts → src/webStreams/WebRtcInterface.ts (remove server event types that belong in native layer; add WebRtcMethodCall discriminated union; import Jsep from ApiTypes) ApiTypes.ts: - Add Jsep interface (sdp + RTCSdpType) replacing the loose string in WebRtcInterface - Add StreamTrack interface (replaces StreamApi-local definition) - Remove dead types: StreamRemoteInfo, StreamList, StreamTrackList, PublishMeta, TrackInfo, StreamRoomInfo, StreamRoomList, TrackType WebRtcInterfaceImpl.ts: - Update import paths to new locations - Replace { [K: string]: Function } methodsMap with typed MethodMap - Fix method bindings with .bind(this) and typed lambda for updateSessionId - methodCall now uses WebRtcMethodCall["name"] as key type EncryptTransform.ts (new src/webStreams/worker/): - Extract per-frame AES-256-GCM logic out of the monolithic worker.ts - Pure class: no worker globals, no postMessage, no module state - encryptFrame takes lastRms as a parameter; decryptFrame returns rms | null worker.ts: - Use EncryptTransform and new WorkerEvents discriminated unions - Add RTCRtpScriptTransform entry point (modern browsers) alongside postMessage - Add stop operation with AbortController-based pipeline cancellation - logPipelineError ignores AbortError in addition to "Destination stream closed" KeyStore.ts: - Make setKeys async (was fire-and-forget; importKeyAndWipeMaterial is async) - Add getEncryptionExternalKeyId() and resolveKeyId() stubs (same value as getEncryptionKeyId for now; session-prefix refactor comes later) Add EncryptTransform.test.ts covering audio/video encrypt+decrypt round-trips, header preservation, tamper detection, and short-frame passthrough. * fix(streaming): ICE trickle serialisation, kind propagation, DataChannelCryptor ICE trickle candidate serialisation (StreamApiNative): - Replace JSON.stringify(candidate) with serializeCandidate() which explicitly reads all 15 RTCIceCandidate fields; toJSON() only emits 4 fields causing bridge-side parse failures for candidates with extension fields RTCRtpScriptTransform kind propagation (WebRtcClient): - Pass kind: track.kind to every encode/decode transform option so the worker selects the correct header-size table (audio=1B, video key=10B, delta=3B) instead of always defaulting to video; fixes silent audio encryption DataChannelCryptor: - Use getEncryptionExternalKeyId() for the wire key ID and resolveKeyId() when passing to CryptoFacade; separates wire format from internal registry key - Extract GCM_TAG_LENGTH_BYTES = 16 named constant (was magic number) - Move Logger to class field (avoids per-call allocation) - Rename keyId → externalKeyId throughout for clarity - Add JSDoc on encrypt/decryptFromWireFormat * refactor(webstreams): decompose WebRtcClient into focused services Splits the 750-line monolithic WebRtcClient into ten single-responsibility services (PublisherManager, SubscriberManager, DataChannelSession, KeySyncManager, PeerConnectionFactory, PeerConnectionManager, E2eeWorker, E2eeTransformManager, AudioManager, RemoteStreamListenerRegistry). WebRtcClient becomes a thin facade. Circular dependencies (PeerConnectionManager→WebRtcClient trickle, PeerConnectionFactory→SubscriberManager.onRemoteTrack, E2eeWorker→AudioManager RMS callback) are broken by resolving lazily inside callback closures in EndpointFactory.createStreamApi. StreamApiNative now takes WebRtcInterfaceImpl directly; bindApiInterface wiring is done in EndpointFactory. Removes dead files: WebWorkerHelper, WebRtcConfig, WebRtcClientTypes, PeerConnectionsManager, Utils. * refactor(ioc): add IoC container, rename api→native, wire EndpointFactory - Rename src/api/ → src/native/ (git mv, all imports updated) - Delete ApiStatic singleton; pass Api explicitly via constructor injection - Add src/ioc/ with Container, Tokens, buildConnectionApis, buildWebRtcClient - Replace EndpointFactory manual wiring with GlobalContainer/ConnectionContainer - Update Connection: private apisRefs, registerApi()/hasApi(), restore freeApis - Update ExtKey static methods to accept Api as first argument - Remove abstract newApi() from BaseNative * fix(webrtc-lifecycle): session-scoped KeyStore, destroyRefs teardown, worker fix - KeyStore: add sessionPrefix (crypto.randomUUID) to prevent cross-session key ID collisions; resolveKeyId() maps external→internal; renamed getEncryptionExternalKeyId() strips prefix for wire format - StreamApi: override destroyRefs() to call client.destroy() on disconnect - StreamApi: tighten Map types, use EndpointTypes.StreamRoomId throughout - BaseApi: remove unused BaseNative import - EventQueue: initialise deferedPromise as null to prevent stale state - ConnectionNative/EventQueueNative: remove now-dead stub newApi() overrides - worker.ts: default kind to "video" when undefined - Add tests: AudioManager, E2eeTransformManager, RemoteStreamListenerRegistry * refactor(audio): translate rms-processor comments to English, add frame throttling Polish the AudioWorkletProcessor: replace Polish inline comments with English, add per-4-frame throttle on postMessage to reduce main-thread pressure. * refactor(service): tighten optional param signatures, make native fields private Remove redundant `| undefined` from optional parameters in CryptoApi and InboxApi; tighten ThreadApi and KvdbApi native fields from protected to private to prevent accidental subclass access. * refactor(rename-disambiguate): delete obsolete types, simplify PrivmxClient caching Remove five dead Janus-era type files (BaseServerTypes, MediaServerWebSocketApiTypes, SignalingReceiverTypes, SignalingSenderTypes, StreamsApiTypes) and the ServerTypes.ts barrel that re-exported them. Strip unused ListQuery and StreamAndTracksSelector from ApiTypes. Simplify PrivmxClient: CryptoApi and EventQueue are now global container singletons so the local Promise caches are redundant. * refactor(crypto): add explicit type casts and fix constructor signature forms Tighten type precision in the crypto layer: explicit `as ArrayBuffer` in Utils, `as string` and `as elliptic.ec.Signature & { recoveryParam }` casts in EmCrypto, `!` non-null assertion for `crypto.subtle`, and `{ new(...): T }` form for constructor-type parameters in assert helpers. * fix: lint fix * devel merge and adjustments * fix(audio): replace activeSince/active with activeUntil in ActiveSpeakerDetector The old hold-off logic tracked `activeSince` (start of continuous speech) and marked speakers inactive once `now - activeSince >= holdMs` — the opposite of the intended behaviour: speakers went silent after holdMs of *continuous* speech, not holdMs after speech *ended*. Replace with `activeUntil = timestamp + holdMs`, reset on every above-threshold frame (aligned with Java PR #145 SpeakingAnalyzer). Callers evaluate `Date.now() <= state.activeUntil` instead of a stale boolean snapshot. Add 25 unit tests covering detection, hold-off, activity window, multi-speaker, pruning, and noise floor adaptation. * ExtKey api fix --------- Co-authored-by: Paweł Aniszewski <paniszewski@simplito.com>
1 parent a954ef5 commit 2a01b7a

83 files changed

Lines changed: 4481 additions & 2686 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 374 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"copy-webpack-plugin": "^14.0.0",
8181
"jest": "^29.7.0",
8282
"mongodb": "^7.0.0",
83+
"oxfmt": "^0.47.0",
8384
"oxlint": "^1.60.0",
8485
"ts-jest": "^29.2.5",
8586
"ts-loader": "^9.5.1",

src/ServerTypes.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/api/ApiStatic.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/crypto/CryptoFacade.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class CryptoFacade {
173173
if (key instanceof Uint8Array || key instanceof ArrayBuffer) {
174174
throw new TypeError(
175175
`CryptoFacade.${method}: Raw key bytes are not allowed. ` +
176-
`Use CryptoFacade.importKey() first to obtain a keyId.`,
176+
"Use CryptoFacade.importKey() first to obtain a keyId.",
177177
);
178178
}
179179
}

src/crypto/EmCrypto.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ export class EmCrypto {
553553
if (params.password instanceof CryptoKey) {
554554
key = params.password;
555555
} else {
556-
const passwordStr = params.password as string;
556+
const passwordStr = params.password;
557557
key = await subtle.importKey(
558558
"raw",
559559
textEncoder.encode(passwordStr) as unknown as BufferSource,
@@ -654,7 +654,10 @@ export class EmCrypto {
654654
assertIsUint8Array(params.signature);
655655
// signature format: [recovery_byte (1B)] [r (32B)] [s (32B)]
656656
const compactSig = params.signature.subarray(1, 65); // 64 bytes: r || s
657-
return secp.verify(compactSig, params.data, params.publicKey, { prehash: false, lowS: false });
657+
return secp.verify(compactSig, params.data, params.publicKey, {
658+
prehash: false,
659+
lowS: false,
660+
});
658661
}
659662

660663
public async eccVerify2(params: Types.Verify2_PARAMS) {
@@ -716,7 +719,9 @@ export class EmCrypto {
716719
assertArgsValid(params, Types.BNeq_PARAMS);
717720
assertIsUint8Array(params.bn);
718721
assertIsUint8Array(params.bn2);
719-
return bytesToBigInt(new Uint8Array(params.bn)) === bytesToBigInt(new Uint8Array(params.bn2));
722+
return (
723+
bytesToBigInt(new Uint8Array(params.bn)) === bytesToBigInt(new Uint8Array(params.bn2))
724+
);
720725
}
721726

722727
public async pointEncode(params: Types.PointEncode_PARAMS) {

src/crypto/Utils.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ export function toArrayBuffer(buffer: Uint8Array | ArrayBuffer): ArrayBuffer {
1313
if (buffer instanceof ArrayBuffer) {
1414
return buffer;
1515
}
16-
return buffer.buffer.slice(
17-
buffer.byteOffset,
18-
buffer.byteOffset + buffer.byteLength,
19-
) as ArrayBuffer;
16+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
2017
}
2118

2219
export function toBuffer(byteArray: ArrayBuffer | Uint8Array): Uint8Array {

src/crypto/assert.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function assertIsNumber(value: unknown): asserts value is number {
2626
if (typeof value !== "number") throw new Error("Not a number");
2727
}
2828

29-
export function assertArgsValid<T>(obj: any, argsType: { new (...args: any[]): T }) {
29+
export function assertArgsValid<T>(obj: any, argsType: new (...args: any[]) => T) {
3030
const objKeys = Object.keys(obj);
3131
const expected = Object.keys(new argsType());
3232
if (!objKeys.every((x) => expected.includes(x))) {
@@ -39,7 +39,7 @@ export function assertArgsValid<T>(obj: any, argsType: { new (...args: any[]): T
3939
}
4040
}
4141

42-
export function assertArgsAndValueValid<T>(actualObj: T, defaultObj: { new (...args: any[]): T }) {
42+
export function assertArgsAndValueValid<T>(actualObj: T, defaultObj: new (...args: any[]) => T) {
4343
const objKeys = Object.keys(actualObj);
4444
const expected = Object.keys(new defaultObj());
4545
if (!objKeys.every((x) => expected.includes(x))) {

src/extra/PrivmxClient.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ import { EventManager } from "./events";
4848
* await client.disconnect();
4949
*/
5050
export class PrivmxClient {
51-
private static cryptoApi: Promise<CryptoApi> | null = null;
52-
private static eventQueue: Promise<EventQueue> | null = null;
5351
private static isSetup = false;
5452
private static eventManager: Promise<EventManager> | null = null;
5553

@@ -97,35 +95,17 @@ export class PrivmxClient {
9795
* @returns {Promise<CryptoApi>}
9896
*/
9997
public static async getCryptoApi(): Promise<CryptoApi> {
100-
if (this.cryptoApi) {
101-
return this.cryptoApi;
102-
}
103-
10498
this.checkSetup();
105-
106-
this.cryptoApi = (async () => {
107-
return EndpointFactory.createCryptoApi();
108-
})();
109-
110-
return this.cryptoApi;
99+
return EndpointFactory.createCryptoApi();
111100
}
112101

113102
/**
114103
* @description Gets the Event Queue.
115104
* @returns {Promise<EventQueue>}
116105
*/
117106
public static async getEventQueue(): Promise<EventQueue> {
118-
if (this.eventQueue) {
119-
return this.eventQueue;
120-
}
121-
122107
this.checkSetup();
123-
124-
this.eventQueue = (async () => {
125-
return EndpointFactory.getEventQueue();
126-
})();
127-
128-
return this.eventQueue;
108+
return EndpointFactory.getEventQueue();
129109
}
130110

131111
/**

src/extra/inbox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface InboxEntryPayload {
1313
/**
1414
* Optional files associated with the entry.
1515
*/
16-
files?: Array<{
16+
files?: {
1717
/**
1818
*Optional, contains confidential data that will be encrypted before being sent to server.
1919
*/
@@ -28,7 +28,7 @@ export interface InboxEntryPayload {
2828
* Content of the file.
2929
*/
3030
content: File;
31-
}>;
31+
}[];
3232
}
3333

3434
/**

0 commit comments

Comments
 (0)