openvidu-testapp: fix JSON event clean up

v2compatibility
pabloFuente 2025-12-26 12:54:30 +01:00
parent 07fcb1a462
commit 082c3a5580
1 changed files with 120 additions and 164 deletions

View File

@ -3,134 +3,7 @@ import { Subject } from "rxjs";
import * as stringify from "json-stringify-safe"; import * as stringify from "json-stringify-safe";
import { import { Event } from "openvidu-browser-v2compatibility";
// Base classes
Session,
Stream,
Connection,
StreamManager,
Publisher,
Subscriber,
// Base Event
Event,
// Session Events
ConnectionEvent,
SessionDisconnectedEvent,
SignalEvent,
StreamEvent,
StreamPropertyChangedEvent,
ConnectionPropertyChangedEvent, // Missed previously
NetworkQualityLevelChangedEvent, // Missed previously
SpeechToTextEvent, // Missed previously
ExceptionEvent,
// StreamManager / Publisher / Subscriber Events
StreamManagerEvent,
VideoElementEvent,
PublisherSpeakingEvent,
RecordingEvent,
FilterEvent, // Missed previously
} from "openvidu-browser-v2compatibility";
const API_ALLOWLIST = new Map<any, string[]>([
// ===========================================================================
// 1. CORE ENTITIES
// ===========================================================================
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Session.html
[Session, ["sessionId", "connection", "capabilities", "streamManagers"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Connection.html
[Connection, ["connectionId", "creationTime", "data", "record", "role"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Stream.html
[
Stream,
[
"streamId",
"creationTime",
"hasAudio",
"hasVideo",
"audioActive",
"videoActive",
"typeOfVideo",
"frameRate",
"videoDimensions",
"connection",
"filter",
],
],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/StreamManager.html
[StreamManager, ["stream", "id", "remote", "videos"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Publisher.html
[Publisher, ["stream", "id", "remote", "videos", "accessAllowed"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Subscriber.html
[Subscriber, ["stream", "id", "remote", "videos"]],
// ===========================================================================
// 2. BASE EVENT
// ===========================================================================
// These are merged into all specific events
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/Event.html
[Event, ["type", "cancelable", "target"]],
// ===========================================================================
// 3. SPECIFIC EVENTS (Alphabetical Order)
// ===========================================================================
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/ConnectionEvent.html
[ConnectionEvent, ["connection", "reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/ConnectionPropertyChangedEvent.html
[
ConnectionPropertyChangedEvent,
["connection", "changedProperty", "oldValue", "newValue", "reason"],
],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/ExceptionEvent.html
[ExceptionEvent, ["name", "message", "data"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/FilterEvent.html
[FilterEvent, ["filter", "eventType", "data"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/NetworkQualityLevelChangedEvent.html
[NetworkQualityLevelChangedEvent, ["connection", "newValue", "oldValue"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/PublisherSpeakingEvent.html
[PublisherSpeakingEvent, ["connection", "streamId", "reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/RecordingEvent.html
[RecordingEvent, ["id", "name", "reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/SessionDisconnectedEvent.html
[SessionDisconnectedEvent, ["reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/SignalEvent.html
[SignalEvent, ["type", "data", "from"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/SpeechToTextEvent.html
[SpeechToTextEvent, ["connection", "text", "reason", "timestamp", "raw"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/StreamEvent.html
[StreamEvent, ["stream", "reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/StreamManagerEvent.html
[StreamManagerEvent, ["stream", "reason"]],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/StreamPropertyChangedEvent.html
[
StreamPropertyChangedEvent,
["stream", "changedProperty", "oldValue", "newValue", "reason"],
],
// https://docs.openvidu.io/en/2.32.0/api/openvidu-browser/classes/VideoElementEvent.html
[VideoElementEvent, ["element"]],
]);
@Injectable() @Injectable()
export class TestFeedService { export class TestFeedService {
@ -152,56 +25,139 @@ export class TestFeedService {
return this.cleanEvent(event); return this.cleanEvent(event);
} }
cleanEvent(root: any): string { cleanEvent(event: any): string {
const seen = new WeakSet(); // 1. GLOBAL BLOCKLIST
const globalBlocklist = new Set([
"ee",
"openvidu",
"userHandlerArrowHandler",
"handlers",
"reliableMessageBuffer",
"socket",
"loggerOptions",
"log",
"closingLock",
"connectionLock",
"offerLock",
"remoteOfferLock",
"disconnectLock",
"dataProcessLock",
"taskMutex",
"requestQueue",
"mutex",
"lock",
"publisherConnectionPromise",
"signalConnectedFuture",
"sifTrailer",
"enabledCodecs",
"supportedCodecs",
"enabledPublishCodecs",
"enabledPublishVideoCodecs",
"videoCaptureDefaults",
"audioCaptureDefaults",
"publishDefaults",
"reconnectPolicy",
"permissions",
"engine",
"client",
"signalClient",
"pcManager",
"ws",
"internalEmitter",
"publicEmmiter",
"codecs",
"layers",
"encodings",
"prevStats",
"monitorInterval",
"volumeMap",
"midToTrackId",
"audioContext",
"rtcConfig",
"options",
"connectOptions",
]);
const getAllowedKeys = (obj: any): string[] | null => { // 2. SCOPED BLOCKLIST (Parent -> Child removal)
for (const [ClassConstructor, keys] of API_ALLOWLIST.entries()) { const scopedBlocklist: Record<string, Set<string>> = {
if (obj instanceof ClassConstructor) { session: new Set(["room", "openvidu"]),
// If instance of specific Event, merge with base Event keys target: new Set(["stream"]),
if (obj instanceof Event && ClassConstructor !== Event) { streamManagers: new Set(["stream"])
return [...(API_ALLOWLIST.get(Event) || []), ...keys];
}
return keys;
}
}
return null;
}; };
const traverse = (current: any): any => { const seen = new WeakSet();
if (current === null || current === undefined) return current;
if (typeof current === "bigint") return current.toString();
if (typeof current !== "object") return current;
if (seen.has(current)) return undefined; // 3. RECURSIVE WALKER
seen.add(current); const traverse = (
current: any,
if (Array.isArray(current)) { nodeName: string | null,
return current.map(traverse).filter((x) => x !== undefined); parentName: string | null
): any => {
// JSON.stringify cannot handle BigInt. Convert it to string.
if (typeof current === "bigint") {
return current.toString();
} }
const allowedKeys = getAllowedKeys(current); // A. Handle Primitives
const copy: any = {}; if (typeof current !== "object" || current === null) {
return current;
}
if (allowedKeys) { // B. Handle Circular References
// Known Class: Filter strictly if (seen.has(current)) {
for (const key of allowedKeys) { return undefined;
if (key in current) { }
const val = traverse(current[key]); seen.add(current);
if (val !== undefined) copy[key] = val;
// C. Handle Arrays
if (Array.isArray(current)) {
return current
.map((item) => traverse(item, null, nodeName))
.filter((item) => item !== undefined);
}
// D. Handle Objects
const copy: any = {};
const effectiveParent = nodeName || parentName;
for (const [childKey, childValue] of Object.entries(current)) {
// Rule 1: Global Blocklist and Private Properties (start with _)
if (globalBlocklist.has(childKey) || childKey.startsWith("_")) {
continue;
}
// Rule 2: Scoped Blocklist
if (
effectiveParent &&
scopedBlocklist[effectiveParent]?.has(childKey)
) {
continue;
}
// Rule 3: Metadata parsing
if (childKey === "metadata" && typeof childValue === "string") {
try {
const parsed = JSON.parse(childValue);
copy[childKey] = {
role: parsed.role,
clientData: parsed.clientData,
};
continue;
} catch {
/* ignore */
} }
} }
} else {
// Plain Object: Copy recursive const cleanedValue = traverse(childValue, childKey, nodeName);
for (const [key, value] of Object.entries(current)) { if (cleanedValue !== undefined) {
const val = traverse(value); copy[childKey] = cleanedValue;
if (val !== undefined) copy[key] = val;
} }
} }
return copy; return copy;
}; };
return JSON.stringify(traverse(root)); const cleanedObject = traverse(event, "root", null);
return JSON.stringify(cleanedObject);
} }
} }