From 1c4e1a5170333ed93f04bc86bcc1f0d59d0d82f4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2025 10:37:30 +0000 Subject: [PATCH 1/3] Remove legacy Safari prefix compatibility for AudioContext Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 3 --- src/audio/Playback.ts | 45 ++++++++++++++---------------------------- src/audio/compat.ts | 5 ----- 3 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index c76c43f8298..1fd5e2f1493 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -80,9 +80,6 @@ declare global { mxMatrixClientPeg: IMatrixClientPeg; mxReactSdkConfig: DeepReadonly; - // Needed for Safari, unknown to TypeScript - webkitAudioContext: typeof AudioContext; - // https://docs.microsoft.com/en-us/previous-versions/hh772328(v=vs.85) // we only ever check for its existence, so we can ignore its actual type MSStream?: unknown; diff --git a/src/audio/Playback.ts b/src/audio/Playback.ts index af777a8c011..fef1b9c1dcd 100644 --- a/src/audio/Playback.ts +++ b/src/audio/Playback.ts @@ -164,36 +164,21 @@ export class Playback extends EventEmitter implements IDestroyable, PlaybackInte this.element.src = URL.createObjectURL(new Blob([this.buf])); await deferred.promise; // make sure the audio element is ready for us } else { - // Safari compat: promise API not supported on this function - this.audioBuf = await new Promise((resolve, reject) => { - this.context.decodeAudioData( - this.buf, - (b) => resolve(b), - async (e): Promise => { - try { - // This error handler is largely for Safari as well, which doesn't support Opus/Ogg - // very well. - logger.error("Error decoding recording: ", e); - logger.warn("Trying to re-encode to WAV instead..."); - - const wav = await decodeOgg(this.buf); - - // noinspection ES6MissingAwait - not needed when using callbacks - this.context.decodeAudioData( - wav, - (b) => resolve(b), - (e) => { - logger.error("Still failed to decode recording: ", e); - reject(e); - }, - ); - } catch (e) { - logger.error("Caught decoding error:", e); - reject(e); - } - }, - ); - }); + try { + this.audioBuf = await this.context.decodeAudioData(this.buf); + } catch (e) { + logger.error("Error decoding recording:", e); + logger.warn("Trying to re-encode to WAV instead..."); + + try { + // This error handler is largely for Safari, which doesn't support Opus/Ogg very well. + const wav = await decodeOgg(this.buf); + this.audioBuf = await this.context.decodeAudioData(wav); + } catch (e) { + logger.error("Error decoding recording:", e); + throw e; + } + } // Update the waveform to the real waveform once we have channel data to use. We don't // exactly trust the user-provided waveform to be accurate... diff --git a/src/audio/compat.ts b/src/audio/compat.ts index 7af6ef68b23..099c614c98c 100644 --- a/src/audio/compat.ts +++ b/src/audio/compat.ts @@ -17,11 +17,6 @@ import { SAMPLE_RATE } from "./VoiceRecording"; export function createAudioContext(opts?: AudioContextOptions): AudioContext { if (window.AudioContext) { return new AudioContext(opts); - } else if (window.webkitAudioContext) { - // While the linter is correct that "a constructor name should not start with - // a lowercase letter", it's also wrong to think that we have control over this. - // eslint-disable-next-line new-cap - return new window.webkitAudioContext(opts); } else { throw new Error("Unsupported browser"); } From f8e1b2fd6aade090fb6ed4be1a5806f6a5e02648 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2025 10:46:48 +0000 Subject: [PATCH 2/3] Remove more legacy webkit/ms/moz support Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 34 ------------------- .../views/elements/LanguageDropdown.tsx | 10 ++---- .../elements/SpellCheckLanguagesDropdown.tsx | 10 ++---- src/components/views/voip/LegacyCallView.tsx | 33 +++--------------- src/languageHandler.tsx | 3 +- 5 files changed, 10 insertions(+), 80 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 1fd5e2f1493..02f50abee4d 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -84,12 +84,6 @@ declare global { // we only ever check for its existence, so we can ignore its actual type MSStream?: unknown; - // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029#issuecomment-869224737 - // https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas - OffscreenCanvas?: { - new (width: number, height: number): OffscreenCanvas; - }; - mxContentMessages: ContentMessages; mxToastStore: ToastStore; mxDeviceListener: DeviceListener; @@ -149,31 +143,10 @@ declare global { fetchWindowIcons?: boolean; } - interface Document { - // Safari & IE11 only have this prefixed: we used prefixed versions - // previously so let's continue to support them for now - webkitExitFullscreen(): Promise; - msExitFullscreen(): Promise; - readonly webkitFullscreenElement: Element | null; - readonly msFullscreenElement: Element | null; - } - - interface Navigator { - userLanguage?: string; - } - interface StorageEstimate { usageDetails?: { [key: string]: number }; } - interface Element { - // Safari & IE11 only have this prefixed: we used prefixed versions - // previously so let's continue to support them for now - webkitRequestFullScreen(options?: FullscreenOptions): Promise; - msRequestFullscreen(options?: FullscreenOptions): Promise; - // scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void; - } - // https://github.com/microsoft/TypeScript/issues/28308#issuecomment-650802278 interface AudioWorkletProcessor { readonly port: MessagePort; @@ -232,11 +205,4 @@ declare global { var mx_rage_store: IndexedDBLogStore; } -// add method which is missing from the node typing -declare module "url" { - interface Url { - format(): string; - } -} - /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/components/views/elements/LanguageDropdown.tsx b/src/components/views/elements/LanguageDropdown.tsx index 743e0df1f70..cbe4b819244 100644 --- a/src/components/views/elements/LanguageDropdown.tsx +++ b/src/components/views/elements/LanguageDropdown.tsx @@ -105,14 +105,8 @@ export default class LanguageDropdown extends React.Component { // default value here too, otherwise we need to handle null / undefined // values between mounting and the initial value propagating - let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); - let value: string | undefined; - if (language) { - value = this.props.value || language; - } else { - language = navigator.language || navigator.userLanguage; - value = this.props.value || language; - } + const language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); + const value = this.props.value ?? language ?? navigator.language; return ( { private dispatcherRef?: string; private contentWrapperRef = createRef(); @@ -119,8 +96,8 @@ export default class LegacyCallView extends React.Component { } public componentWillUnmount(): void { - if (getFullScreenElement()) { - exitFullscreen(); + if (document.fullscreenElement) { + document.exitFullscreen(); } document.removeEventListener("keydown", this.onNativeKeyDown); @@ -159,9 +136,9 @@ export default class LegacyCallView extends React.Component { return; } if (payload.fullscreen) { - requestFullscreen(this.contentWrapperRef.current); - } else if (getFullScreenElement()) { - exitFullscreen(); + this.contentWrapperRef.current?.requestFullscreen(); + } else if (document.fullscreenElement) { + document.exitFullscreen(); } break; } diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 937a6c18670..42450082cd8 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -529,8 +529,7 @@ export async function getAllLanguagesWithLabels(): Promise { export function getLanguagesFromBrowser(): readonly string[] { if (navigator.languages && navigator.languages.length) return navigator.languages; - if (navigator.language) return [navigator.language]; - return [navigator.userLanguage || "en"]; + return [navigator.language ?? "en"]; } export function getLanguageFromBrowser(): string { From 1459158c898c8e0b3dd4e3c731af177dc390a807 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 Jan 2025 11:15:56 +0000 Subject: [PATCH 3/3] Fix tests Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- test/unit-tests/audio/Playback-test.ts | 6 +++--- .../views/audio_messages/RecordingPlayback-test.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit-tests/audio/Playback-test.ts b/test/unit-tests/audio/Playback-test.ts index 0aeea5c8322..7f14685f450 100644 --- a/test/unit-tests/audio/Playback-test.ts +++ b/test/unit-tests/audio/Playback-test.ts @@ -47,7 +47,7 @@ describe("Playback", () => { beforeEach(() => { jest.spyOn(logger, "error").mockRestore(); mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData); - mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer)); + mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer); mockAudioContext.resume.mockClear().mockResolvedValue(undefined); mockAudioContext.suspend.mockClear().mockResolvedValue(undefined); mocked(decodeOgg).mockClear().mockResolvedValue(new ArrayBuffer(1)); @@ -131,8 +131,8 @@ describe("Playback", () => { const buffer = new ArrayBuffer(8); const decodingError = new Error("test"); mockAudioContext.decodeAudioData - .mockImplementationOnce((_b, _callback, error) => error(decodingError)) - .mockImplementationOnce((_b, callback) => callback(mockAudioBuffer)); + .mockRejectedValueOnce(decodingError) + .mockResolvedValueOnce(mockAudioBuffer); const playback = new Playback(buffer); diff --git a/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx b/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx index 6c96aacb8e8..b5685311017 100644 --- a/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx +++ b/test/unit-tests/components/views/audio_messages/RecordingPlayback-test.tsx @@ -65,7 +65,7 @@ describe("", () => { beforeEach(() => { jest.spyOn(logger, "error").mockRestore(); mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData); - mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer)); + mockAudioContext.decodeAudioData.mockReset().mockResolvedValue(mockAudioBuffer); mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext); });