From 8150cbdec4a3a0a65604a7ce4d8b1d282db239fd Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Thu, 20 Aug 2026 10:35:08 +0200 Subject: [PATCH 1/4] fix: load and allow suppression with rnnoise in secure context Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev --- src/components/MediaSettings/AdvancedAudioDialog.vue | 5 +++-- src/utils/suppressNoise.ts | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/MediaSettings/AdvancedAudioDialog.vue b/src/components/MediaSettings/AdvancedAudioDialog.vue index a0581d146d8..aa507e2c511 100644 --- a/src/components/MediaSettings/AdvancedAudioDialog.vue +++ b/src/components/MediaSettings/AdvancedAudioDialog.vue @@ -14,6 +14,7 @@ import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton' import { useDevices } from '../../composables/useDevices.js' import { useSettingsStore } from '../../stores/settings.ts' import { isSafari } from '../../utils/browserCheck.ts' +import { isNoiseSuppressionModelSupported } from '../../utils/suppressNoise.ts' import { localMediaModel } from '../../utils/webrtc/index.js' const { container = undefined } = defineProps<{ @@ -53,7 +54,7 @@ const settingsStore = useSettingsStore() const { audioPreviewAvailable, updateAudioStream } = useDevices() const noiseSuppressionLevel = computed(() => { - if (settingsStore.noiseSuppressionWithModel === 'none') { + if (!isNoiseSuppressionModelSupported || settingsStore.noiseSuppressionWithModel === 'none') { return settingsStore.noiseSuppression ? NOISE_LEVEL.BASIC : NOISE_LEVEL.OFF } return NOISE_LEVEL.ADVANCED @@ -145,7 +146,7 @@ function onClosing(result?: unknown) { @update:modelValue="setNoiseSuppressionLevel"> - + diff --git a/src/utils/suppressNoise.ts b/src/utils/suppressNoise.ts index 0a4d88322b5..51d7e1d2055 100644 --- a/src/utils/suppressNoise.ts +++ b/src/utils/suppressNoise.ts @@ -3,7 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { loadRnnoise, RnnoiseWorkletNode } from '@sapphi-red/web-noise-suppressor' +import type { RnnoiseWorkletNode } from '@sapphi-red/web-noise-suppressor' + +// The rnnoise model relies on AudioWorkletNode, which only exists in a secure context +export const isNoiseSuppressionModelSupported = 'isSecureContext' in window && window.isSecureContext let audioContext: AudioContext | null = null let rnnoiseWorklet: RnnoiseWorkletNode | null = null @@ -31,6 +34,9 @@ export async function registerNoiseSuppressionWorklet(): Promise } try { + // Lazy load in try-catch block (AudioWorkletNode does not exist in an insecure context) + const { loadRnnoise, RnnoiseWorkletNode } = await import('@sapphi-red/web-noise-suppressor') + audioContext = new AudioContext() const rnnoiseWasmBinary = await loadRnnoise({ url: new URL( From 8b7f18d4d7a4bbcf01b7c918782ccdc721a36340 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Thu, 20 Aug 2026 10:39:35 +0200 Subject: [PATCH 2/4] fix: only show voice message recorder in secure context Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev --- src/components/NewMessage/NewMessage.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 33945770401..fd583f7c4fe 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -381,6 +381,7 @@ import { selectRange, } from '../../utils/selectionRange.ts' import { parseSpecialSymbols } from '../../utils/textParse.ts' +import { mediaDevicesManager } from '../../utils/webrtc/index.js' const supportScheduleMessages = hasTalkFeature('local', 'scheduled-messages') @@ -670,7 +671,8 @@ export default { }, showAudioRecorder() { - return !this.hasText && this.canUploadFiles && !this.broadcast && !this.upload + return mediaDevicesManager.isSupported() + && !this.hasText && this.canUploadFiles && !this.broadcast && !this.upload && !this.messageToEdit && !this.threadCreating && !this.scheduleMessageTime && !this.showScheduledMessages }, From 7803147bf033f732fc70d58631035d703caeb3ea Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Thu, 20 Aug 2026 10:46:13 +0200 Subject: [PATCH 3/4] fix: guard call encryption support for secure context Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev --- src/utils/e2ee/encryption.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/e2ee/encryption.js b/src/utils/e2ee/encryption.js index da89ea75526..9a8f6a864f6 100644 --- a/src/utils/e2ee/encryption.js +++ b/src/utils/e2ee/encryption.js @@ -44,6 +44,10 @@ class Encryption { throw new Error('stream transform is not supported') } + if (!window.crypto?.subtle) { + throw new Error('crypto.subtle is not available in this context') + } + await initializeOlm() return true } From 06b07a78def1d89babde001079b18e607089f3c5 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Thu, 20 Aug 2026 10:49:42 +0200 Subject: [PATCH 4/4] fix: add fallback for file upload when not in secure context Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Maksim Sukharev --- src/stores/upload.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/stores/upload.ts b/src/stores/upload.ts index 2a8977e1430..51d2c8e1722 100644 --- a/src/stores/upload.ts +++ b/src/stores/upload.ts @@ -15,6 +15,7 @@ import { showError } from '@nextcloud/dialogs' import { t } from '@nextcloud/l10n' import { getUploader } from '@nextcloud/upload' import { defineStore } from 'pinia' +import { v4 as uuidv4 } from 'uuid' import { reactive, ref } from 'vue' import { useStore } from 'vuex' import { useTemporaryMessage } from '../composables/useTemporaryMessage.ts' @@ -469,7 +470,8 @@ export const useUploadStore = defineStore('upload', () => { // resolves the final name (and any conflicts) when postAttachment // moves the file out of Draft. for (const [index] of getInitialisedUploads(uploadId)) { - const tempName = crypto.randomUUID() + // crypto.randomUUID() is not available in insecure context + const tempName = crypto.randomUUID ? crypto.randomUUID() : uuidv4() markFileAsPendingUpload({ uploadId, index, sharePath: '/' + draftFolderPath + '/' + tempName }) } return