Skip to content
Merged
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
1 change: 1 addition & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"/*\nCopyright %%CURRENT_YEAR%% Element Creations Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n"
],
"element-call/no-observablescope-leak": "error",
"element-call/no-top-level-logger-get-child": "error",
"jsdoc/empty-tags": "error",
"jsdoc/check-property-names": "error",
"jsdoc/require-param-description": "warn",
Expand Down
92 changes: 92 additions & 0 deletions eslint/NoTopLevelLoggerGetChild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright 2026 Element Creations Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/

import { ESLintUtils } from "@typescript-eslint/utils";

/**
* Node types that introduce a new non-module scope. A getChild() call nested
* inside any of these is considered "not at the top level".
*/
const FUNCTION_OR_CLASS_TYPES = new Set([
"FunctionDeclaration",
"FunctionExpression",
"ArrowFunctionExpression",
"ClassBody",
]);

const rule = ESLintUtils.RuleCreator(
() => "https://github.com/element-hq/element-call",
)({
name: "no-top-level-logger-get-child",
meta: {
type: "problem",
docs: {
description:
"Disallow calling logger.getChild() at the top level of a module." +
"`getChild` has to be called after the rageshake logger `init()`." +
"If it is called at the top level the child logger will never be setup for rageshakes.",
},
messages: {
noTopLevelGetChild:
"Do not call logger.getChild() at the top level of a module; move it inside a function or class instead that gets called after rageshake logger `init()` is called.",
},
schema: [],
},
create(context) {
// Tracks the local binding names that refer to the logger imported from
// 'matrix-js-sdk/lib/logger', e.g. both `logger` and `rootLogger` in:
// import { logger } from "matrix-js-sdk/lib/logger";
// import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
const loggerNames = new Set();

return {
ImportDeclaration(node) {
if (node.source.value !== "matrix-js-sdk/lib/logger") return;
for (const specifier of node.specifiers) {
if (
specifier.type === "ImportSpecifier" &&
specifier.imported.name === "logger"
) {
loggerNames.add(specifier.local.name);
}
}
},

CallExpression(node) {
// Must be a non-computed member expression call: something.getChild(...)
if (
node.callee.type !== "MemberExpression" ||
node.callee.computed ||
node.callee.property.type !== "Identifier" ||
node.callee.property.name !== "getChild"
)
return;

// The receiver must be one of the tracked logger names.
const object = node.callee.object;
if (object.type !== "Identifier" || !loggerNames.has(object.name))
return;

// Flag the call only when it is at module top level — i.e. there is no
// enclosing function or class body anywhere in the ancestor chain.
const ancestors = context.sourceCode.getAncestors(node);
const isTopLevel = !ancestors.some((a) =>
FUNCTION_OR_CLASS_TYPES.has(a.type),
);

if (isTopLevel) {
context.report({
messageId: "noTopLevelGetChild",
node,
});
}
},
};
},
});

export default rule;
2 changes: 2 additions & 0 deletions eslint/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ module.exports = {
rules: {
"copyright-header": require("./CopyrightHeader").default,
"no-observablescope-leak": require("./NoObservableScopeLeak").default,
"no-top-level-logger-get-child": require("./NoTopLevelLoggerGetChild")
.default,
},
};
3 changes: 1 addition & 2 deletions sdk/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ import { scan } from "rxjs";
import { type WidgetHelpers } from "../src/widget";
import { type LivekitRoomItem } from "../src/state/CallViewModel/CallViewModel";

export const logger = rootLogger.getChild("[MatrixRTCSdk]");

export const tryMakeSticky = (widget: WidgetHelpers): void => {
const logger = rootLogger.getChild("[MatrixRTCSdk]");
logger.info("try making sticky MatrixRTCSdk");
void widget.api
.setAlwaysOnScreen(true)
Expand Down
4 changes: 3 additions & 1 deletion sdk/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import { getUrlParams } from "../src/UrlParams";
import { MuteStates } from "../src/state/MuteStates";
import { MediaDevices } from "../src/state/MediaDevices";
import { E2eeType } from "../src/e2ee/e2eeType";
import { currentAndPrev, logger, TEXT_LK_TOPIC, tryMakeSticky } from "./helper";
import { currentAndPrev, TEXT_LK_TOPIC, tryMakeSticky } from "./helper";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import {
ElementWidgetActions,
widget as _widget,
Expand Down Expand Up @@ -104,6 +105,7 @@ export async function createMatrixRTCSdk(
id: string = "",
sticky: boolean = false,
): Promise<MatrixRTCSdk> {
const logger = rootLogger.getChild("[MatrixRTCSdk]");
const scope = new ObservableScope();

// widget client
Expand Down
10 changes: 5 additions & 5 deletions src/e2ee/matrixKeyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import {
type MatrixRTCSession,
MatrixRTCSessionEvent,
} from "matrix-js-sdk/lib/matrixrtc";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger";
import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
const logger = rootLogger.getChild("[MatrixKeyProvider]");

export class MatrixKeyProvider extends BaseKeyProvider {
private rtcSession?: MatrixRTCSession;

private logger: Logger;
public constructor() {
super({ ratchetWindowSize: 10, keyringSize: 256 });
this.logger = rootLogger.getChild("[MatrixKeyProvider]");
}

public setRTCSession(rtcSession: MatrixRTCSession): void {
Expand Down Expand Up @@ -60,12 +60,12 @@ export class MatrixKeyProvider extends BaseKeyProvider {
encryptionKeyIndex,
);

logger.debug(
this.logger.debug(
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${rtcBackendIdentity} (before hash: ${membershipParts.userId}:${membershipParts.deviceId}) encryptionKeyIndex=${encryptionKeyIndex}`,
);
},
(e) => {
logger.error(
this.logger.error(
`Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId before hash=${membershipParts.userId}:${membershipParts.deviceId} encryptionKeyIndex=${encryptionKeyIndex}`,
e,
);
Expand Down
6 changes: 3 additions & 3 deletions src/livekit/MatrixAudioRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
AudioTrack,
type AudioTrackProps,
} from "@livekit/components-react";
import { logger } from "matrix-js-sdk/lib/logger";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";

import { useEarpieceAudioConfig } from "../MediaDevicesContext";
import { useReactiveState } from "../useReactiveState";
Expand All @@ -40,7 +40,6 @@ export interface MatrixAudioRendererProps {
muted?: boolean;
}

const prefixedLogger = logger.getChild("[MatrixAudioRenderer]");
/**
* Takes care of handling remote participants’ audio tracks and makes sure that microphones and screen share are audible.
*
Expand All @@ -60,6 +59,7 @@ export function LivekitRoomAudioRenderer({
validIdentities,
muted,
}: MatrixAudioRendererProps): ReactNode {
const logger = rootLogger.getChild("[MatrixAudioRenderer]");
const tracks = useTracks(
[
Track.Source.Microphone,
Expand All @@ -80,7 +80,7 @@ export function LivekitRoomAudioRenderer({
if (!isValid) {
// TODO make sure to also skip the warn logging for the local identity
// Log that there is an invalid identity, that means that someone is publishing audio that is not expected to be in the call.
prefixedLogger.warn(
logger.warn(
`Audio track ${ref.participant.identity} from ${url} has no matching matrix call member`,
`current members: ${validIdentities.join()}`,
`track will not get rendered`,
Expand Down
5 changes: 2 additions & 3 deletions src/room/InCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ declare module "react" {
}
}

const logger = rootLogger.getChild("[InCallView]");

export interface ActiveCallProps extends Omit<
InCallViewProps,
"vm" | "livekitRoom" | "connState" | "footerVm"
Expand All @@ -116,7 +114,7 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
const mediaDevices = useMediaDevices();
const trackProcessorState$ = useTrackProcessorObservable$();
useEffect(() => {
logger.info("START CALL VIEW SCOPE");
rootLogger.info("START CALL VIEW SCOPE");
const scope = new ObservableScope();
const reactionsReader = new ReactionsReader(scope, props.rtcSession);
const { autoLeaveWhenOthersLeft, waitForCallPickup, sendNotificationType } =
Expand Down Expand Up @@ -218,6 +216,7 @@ export const InCallView: FC<InCallViewProps> = ({
muteStates,
onShareClick,
}) => {
const logger = rootLogger.getChild("[InCallView]");
const { t } = useTranslation();
const { sendReaction, toggleRaisedHand } = useReactionsSender();

Expand Down
5 changes: 3 additions & 2 deletions src/settings/rageshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ declare global {
// eslint-disable-next-line no-var, camelcase
var mx_rage_initStoragePromise: Promise<void> | undefined;
}

export let rageshakeLogger: Logger;
/**
* Configure rage shaking support for sending bug reports.
* Modifies globals.
Expand All @@ -477,7 +477,8 @@ export async function init(): Promise<void> {
global.mx_rage_logger = new ConsoleLogger();

// configure loglevel based loggers:
setLogExtension(logger, global.mx_rage_logger.log);
rageshakeLogger = logger;
setLogExtension(rageshakeLogger, global.mx_rage_logger.log);

// intercept console logging so that we can get matrix_sdk logs:
// this is nasty, but no logging hooks are provided
Expand Down
3 changes: 1 addition & 2 deletions src/state/CallViewModel/CallNotificationLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ import { type Behavior } from "../Behavior";
import { type Epoch, type ObservableScope } from "../ObservableScope";
import { type RoomMemberMap } from "./remoteMembers/MatrixMemberMetadata";

const logger = rootLogger.getChild("[CallNotificationLifecycle]");

export type AutoLeaveReason = "allOthersLeft" | "timeout" | "decline";

export interface RingAttempt {
Expand Down Expand Up @@ -114,6 +112,7 @@ export function createCallNotificationLifecycle$({
*/
autoLeave$: Observable<AutoLeaveReason>;
} {
const logger = rootLogger.getChild("[CallNotificationLifecycle]");
let ringAttempts$: Observable<RingAttempt> = NEVER;
if (options.waitForCallPickup)
ringAttempts$ = sentCallNotification$.pipe(
Expand Down
6 changes: 4 additions & 2 deletions src/state/CallViewModel/CallViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
timer,
takeUntil,
} from "rxjs";
import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
import { type Logger, logger as rootLogger } from "matrix-js-sdk/lib/logger";
import {
MembershipManagerEvent,
type LivekitTransportConfig,
Expand Down Expand Up @@ -157,7 +157,6 @@ import {
} from "../media/RingingMediaViewModel.ts";
import { type GridTileViewModel } from "../TileViewModel.ts";

const logger = rootLogger.getChild("[CallViewModel]");
//TODO
// Larger rename
// member,membership -> rtcMember
Expand Down Expand Up @@ -411,6 +410,7 @@ export function createCallViewModel$(
reactionsSubject$: Observable<Record<string, ReactionInfo>>,
trackProcessorState$: Behavior<ProcessorState>,
): CallViewModel {
const logger = rootLogger.getChild("[CallViewModel]");
const client = matrixRoom.client;
const userId = client.getUserId();
const deviceId = client.getDeviceId();
Expand All @@ -420,6 +420,7 @@ export function createCallViewModel$(
const livekitKeyProvider = getE2eeKeyProvider(
options.encryptionSystem,
matrixRTCSession,
logger,
);
// matrix_rtc_mode in config.json overrides the user's Developer Settings choice.
// It is validated at config load (src/config/Config.ts) so the cast is safe.
Expand Down Expand Up @@ -1797,6 +1798,7 @@ export function createCallViewModel$(
function getE2eeKeyProvider(
e2eeSystem: EncryptionSystem,
rtcSession: MatrixRTCSession,
logger: Logger,
): BaseKeyProvider | undefined {
if (e2eeSystem.kind === E2eeType.NONE) return undefined;

Expand Down
6 changes: 1 addition & 5 deletions src/state/CallViewModel/localMember/HomeserverConnected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ import { type ObservableScope } from "../../ObservableScope";
import { type Behavior } from "../../Behavior";
import { type NodeStyleEventEmitter } from "../../../utils/test";

/**
* Logger instance (scoped child) for homeserver connection updates.
*/
const logger = rootLogger.getChild("[HomeserverConnected]");

export type HomeserverDisconnectReason = "sync" | "membership" | "probablyLeft";

export interface HomeserverConnected {
Expand Down Expand Up @@ -70,6 +65,7 @@ export function createHomeserverConnected$(
Pick<MatrixRTCSession, "membershipStatus" | "probablyLeft">,
gracePeriodMs?: number,
): HomeserverConnected {
const logger = rootLogger.getChild("[HomeserverConnected]");
// Get grace period from parameter or config (default 10000ms)
const graceMs = gracePeriodMs ?? Config.get().sync_disconnect_grace_period_ms;

Expand Down
Loading
Loading