diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index 104e073ef..caeac8286 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -15,9 +15,9 @@ jobs: security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7 diff --git a/.oxlintrc.json b/.oxlintrc.json index 819e54396..d06f2a473 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -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", diff --git a/eslint/NoTopLevelLoggerGetChild.js b/eslint/NoTopLevelLoggerGetChild.js new file mode 100644 index 000000000..3d9752c47 --- /dev/null +++ b/eslint/NoTopLevelLoggerGetChild.js @@ -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; diff --git a/eslint/index.js b/eslint/index.js index ec050ad76..1f2e7629e 100644 --- a/eslint/index.js +++ b/eslint/index.js @@ -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, }, }; diff --git a/locales/en/app.json b/locales/en/app.json index 8656502cf..3ff956a6a 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -205,6 +205,7 @@ "blur_not_supported_by_browser": "(Background blur is not supported by this device.)", "developer_tab_title": "Developer", "devices": { + "activating": "Activating…", "camera": "Camera", "camera_numbered": "Camera {{n}}", "change_device_button": "Change audio device", diff --git a/sdk/helper.ts b/sdk/helper.ts index 47de4a939..9f7e02a7d 100644 --- a/sdk/helper.ts +++ b/sdk/helper.ts @@ -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) diff --git a/sdk/main.ts b/sdk/main.ts index b94a7eb51..a001af65c 100644 --- a/sdk/main.ts +++ b/sdk/main.ts @@ -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, @@ -104,6 +105,7 @@ export async function createMatrixRTCSdk( id: string = "", sticky: boolean = false, ): Promise { + const logger = rootLogger.getChild("[MatrixRTCSdk]"); const scope = new ObservableScope(); // widget client diff --git a/src/__snapshots__/AppBar.test.tsx.snap b/src/__snapshots__/AppBar.test.tsx.snap index 482189481..99b322ac3 100644 --- a/src/__snapshots__/AppBar.test.tsx.snap +++ b/src/__snapshots__/AppBar.test.tsx.snap @@ -3,12 +3,12 @@ exports[`AppBar > renders 1`] = `
@@ -46,12 +46,12 @@ exports[`AppBar > renders 1`] = ` exports[`AppBar > renders with title and subtitle 1`] = `

Title

Subtitle
diff --git a/src/__snapshots__/Modal.test.tsx.snap b/src/__snapshots__/Modal.test.tsx.snap index 648bdfd50..4753701eb 100644 --- a/src/__snapshots__/Modal.test.tsx.snap +++ b/src/__snapshots__/Modal.test.tsx.snap @@ -3,7 +3,7 @@ exports[`the content is rendered when the modal is open 1`] = `