typescript src/settings
This commit is contained in:
parent
9676014120
commit
190c57e853
4 changed files with 236 additions and 110 deletions
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/*
|
||||
Copyright 2022 Matrix.org Foundation C.I.C.
|
||||
|
||||
|
@ -15,6 +16,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
import { TabContainer, TabItem } from "../tabs/Tabs";
|
||||
|
@ -22,15 +25,23 @@ import { ReactComponent as AudioIcon } from "../icons/Audio.svg";
|
|||
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
|
||||
import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg";
|
||||
import { SelectInput } from "../input/SelectInput";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { useMediaHandler } from "./useMediaHandler";
|
||||
import {
|
||||
MediaHandlerContextInterface,
|
||||
useMediaHandler,
|
||||
} from "./useMediaHandler";
|
||||
import { useSpatialAudio, useShowInspector } from "./useSetting";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { useDownloadDebugLog } from "./submit-rageshake";
|
||||
import { Body } from "../typography/Typography";
|
||||
|
||||
export const SettingsModal = (props) => {
|
||||
interface Props {
|
||||
setShowInspector: boolean;
|
||||
showInspector: boolean;
|
||||
[rest: string]: unknown;
|
||||
}
|
||||
|
||||
export const SettingsModal = (props: Props) => {
|
||||
const {
|
||||
audioInput,
|
||||
audioInputs,
|
||||
|
@ -42,6 +53,7 @@ export const SettingsModal = (props) => {
|
|||
audioOutputs,
|
||||
setAudioOutput,
|
||||
} = useMediaHandler();
|
||||
|
||||
const [spatialAudio, setSpatialAudio] = useSpatialAudio();
|
||||
const [showInspector, setShowInspector] = useShowInspector();
|
||||
|
||||
|
@ -90,7 +102,8 @@ export const SettingsModal = (props) => {
|
|||
label="Spatial audio (experimental)"
|
||||
type="checkbox"
|
||||
checked={spatialAudio}
|
||||
onChange={(e) => setSpatialAudio(e.target.checked)}
|
||||
// @ts-ignore
|
||||
onChange={(event: Event) => setSpatialAudio(event.target.checked)}
|
||||
/>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
|
@ -132,7 +145,8 @@ export const SettingsModal = (props) => {
|
|||
label="Show Call Inspector"
|
||||
type="checkbox"
|
||||
checked={showInspector}
|
||||
onChange={(e) => setShowInspector(e.target.checked)}
|
||||
// @ts-ignore
|
||||
onChange={(e: Event) => setShowInspector(e.target.checked)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/*
|
||||
Copyright 2017 OpenMarket Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
@ -37,37 +38,60 @@ limitations under the License.
|
|||
// actually timestamps. We then purge the remaining logs. We also do this
|
||||
// purge on startup to prevent logs from accumulating.
|
||||
|
||||
// the frequency with which we flush to indexeddb
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
// the frequency with which we flush to indexeddb
|
||||
const FLUSH_RATE_MS = 30 * 1000;
|
||||
|
||||
// the length of log data we keep in indexeddb (and include in the reports)
|
||||
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
|
||||
|
||||
// A class which monkey-patches the global console and stores log lines.
|
||||
|
||||
interface LogEntry {
|
||||
id: string;
|
||||
lines: Array<string>;
|
||||
index: number;
|
||||
}
|
||||
|
||||
interface Cursor {
|
||||
id: string;
|
||||
ts: number;
|
||||
}
|
||||
|
||||
// interface CustomEventTarget extends EventTarget {
|
||||
// result: Cursor;
|
||||
// }
|
||||
export class ConsoleLogger {
|
||||
logs = "";
|
||||
|
||||
monkeyPatch(consoleObj) {
|
||||
monkeyPatch(consoleObj: Console): void {
|
||||
// Monkey-patch console logging
|
||||
const consoleFunctionsToLevels = {
|
||||
|
||||
const consoleFunctionsToLevels: { [level: string]: string } = {
|
||||
log: "I",
|
||||
info: "I",
|
||||
warn: "W",
|
||||
error: "E",
|
||||
};
|
||||
Object.keys(consoleFunctionsToLevels).forEach((fnName) => {
|
||||
const level = consoleFunctionsToLevels[fnName];
|
||||
const originalFn = consoleObj[fnName].bind(consoleObj);
|
||||
consoleObj[fnName] = (...args) => {
|
||||
this.log(level, ...args);
|
||||
originalFn(...args);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
log(level, ...args) {
|
||||
Object.keys(consoleFunctionsToLevels).forEach(
|
||||
(fnName: "log" | "info" | "warn" | "error") => {
|
||||
const level = consoleFunctionsToLevels[fnName];
|
||||
const originalFn = consoleObj[fnName].bind(consoleObj);
|
||||
consoleObj[fnName] = (...args: unknown[]) => {
|
||||
this.log(level, ...args);
|
||||
originalFn(...args);
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
// these functions get overwritten by the monkey patch
|
||||
error(...args: unknown[]): void {}
|
||||
warn(...args: unknown[]): void {}
|
||||
info(...args: unknown[]): void {}
|
||||
|
||||
log(level: string, ...args: unknown[]): void {
|
||||
// We don't know what locale the user may be running so use ISO strings
|
||||
const ts = new Date().toISOString();
|
||||
|
||||
|
@ -113,10 +137,10 @@ export class ConsoleLogger {
|
|||
|
||||
/**
|
||||
* Retrieve log lines to flush to disk.
|
||||
* @param {boolean} keepLogs True to not delete logs after flushing.
|
||||
* @param {boolean} keepLogs True to not delete logs after flushing. Defaults to false.
|
||||
* @return {string} \n delimited log lines to flush.
|
||||
*/
|
||||
flush(keepLogs) {
|
||||
flush(keepLogs = false): string {
|
||||
// The ConsoleLogger doesn't care how these end up on disk, it just
|
||||
// flushes them to the caller.
|
||||
if (keepLogs) {
|
||||
|
@ -131,11 +155,14 @@ export class ConsoleLogger {
|
|||
// A class which stores log lines in an IndexedDB instance.
|
||||
export class IndexedDBLogStore {
|
||||
index = 0;
|
||||
db = null;
|
||||
flushPromise = null;
|
||||
flushAgainPromise = null;
|
||||
db: IDBDatabase = null;
|
||||
flushPromise: Promise<void> = null;
|
||||
flushAgainPromise: Promise<void> = null;
|
||||
indexedDB: IDBFactory;
|
||||
logger: ConsoleLogger;
|
||||
id: string;
|
||||
|
||||
constructor(indexedDB, logger) {
|
||||
constructor(indexedDB: IDBFactory, logger: ConsoleLogger) {
|
||||
this.indexedDB = indexedDB;
|
||||
this.logger = logger;
|
||||
this.id = "instance-" + Math.random() + Date.now();
|
||||
|
@ -144,10 +171,10 @@ export class IndexedDBLogStore {
|
|||
/**
|
||||
* @return {Promise} Resolves when the store is ready.
|
||||
*/
|
||||
connect() {
|
||||
connect(): Promise<void> {
|
||||
const req = this.indexedDB.open("logs");
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = (event) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
req.onsuccess = (event: Event) => {
|
||||
// @ts-ignore
|
||||
this.db = event.target.result;
|
||||
// Periodically flush logs to local storage / indexeddb
|
||||
|
@ -155,16 +182,16 @@ export class IndexedDBLogStore {
|
|||
resolve();
|
||||
};
|
||||
|
||||
req.onerror = (event) => {
|
||||
req.onerror = (event: Event) => {
|
||||
const err =
|
||||
// @ts-ignore
|
||||
"Failed to open log database: " + event.target.error.name;
|
||||
logger.error(err);
|
||||
this.logger.error(err);
|
||||
reject(new Error(err));
|
||||
};
|
||||
|
||||
// First time: Setup the object store
|
||||
req.onupgradeneeded = (event) => {
|
||||
req.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
||||
// @ts-ignore
|
||||
const db = event.target.result;
|
||||
const logObjStore = db.createObjectStore("logs", {
|
||||
|
@ -176,7 +203,7 @@ export class IndexedDBLogStore {
|
|||
logObjStore.createIndex("id", "id", { unique: false });
|
||||
|
||||
logObjStore.add(
|
||||
this.generateLogEntry(new Date() + " ::: Log database was created.")
|
||||
this.generateLogEntry([new Date() + " ::: Log database was created."])
|
||||
);
|
||||
|
||||
const lastModifiedStore = db.createObjectStore("logslastmod", {
|
||||
|
@ -206,7 +233,7 @@ export class IndexedDBLogStore {
|
|||
*
|
||||
* @return {Promise} Resolved when the logs have been flushed.
|
||||
*/
|
||||
flush() {
|
||||
flush(): Promise<void> {
|
||||
// check if a flush() operation is ongoing
|
||||
if (this.flushPromise) {
|
||||
if (this.flushAgainPromise) {
|
||||
|
@ -225,7 +252,7 @@ export class IndexedDBLogStore {
|
|||
}
|
||||
// there is no flush promise or there was but it has finished, so do
|
||||
// a brand new one, destroying the chain which may have been built up.
|
||||
this.flushPromise = new Promise((resolve, reject) => {
|
||||
this.flushPromise = new Promise<void>((resolve, reject) => {
|
||||
if (!this.db) {
|
||||
// not connected yet or user rejected access for us to r/w to the db.
|
||||
reject(new Error("No connected database"));
|
||||
|
@ -242,10 +269,11 @@ export class IndexedDBLogStore {
|
|||
resolve();
|
||||
};
|
||||
txn.onerror = (event) => {
|
||||
logger.error("Failed to flush logs : ", event);
|
||||
this.logger.error("Failed to flush logs : ", event);
|
||||
// @ts-ignore
|
||||
reject(new Error("Failed to write logs: " + event.target.errorCode));
|
||||
};
|
||||
objStore.add(this.generateLogEntry(lines));
|
||||
objStore.add(this.generateLogEntry(lines.split("\n")));
|
||||
const lastModStore = txn.objectStore("logslastmod");
|
||||
lastModStore.put(this.generateLastModifiedTime());
|
||||
}).then(() => {
|
||||
|
@ -264,12 +292,13 @@ export class IndexedDBLogStore {
|
|||
* log ID). The objects have said log ID in an "id" field and "lines" which
|
||||
* is a big string with all the new-line delimited logs.
|
||||
*/
|
||||
async consume() {
|
||||
|
||||
async consume(): Promise<Object[]> {
|
||||
const db = this.db;
|
||||
|
||||
// Returns: a string representing the concatenated logs for this ID.
|
||||
// Stops adding log fragments when the size exceeds maxSize
|
||||
function fetchLogs(id, maxSize) {
|
||||
function fetchLogs(id: string, maxSize: number): Promise<string[]> {
|
||||
const objectStore = db
|
||||
.transaction("logs", "readonly")
|
||||
.objectStore("logs");
|
||||
|
@ -280,34 +309,35 @@ export class IndexedDBLogStore {
|
|||
.openCursor(IDBKeyRange.only(id), "prev");
|
||||
let lines = "";
|
||||
query.onerror = (event) => {
|
||||
// @ts-ignore
|
||||
reject(new Error("Query failed: " + event.target.errorCode));
|
||||
};
|
||||
query.onsuccess = (event) => {
|
||||
// @ts-ignore
|
||||
const cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
resolve(lines);
|
||||
resolve(lines.split("\n"));
|
||||
return; // end of results
|
||||
}
|
||||
lines = cursor.value.lines + lines;
|
||||
if (lines.length >= maxSize) {
|
||||
resolve(lines);
|
||||
resolve(lines.split("\n"));
|
||||
} else {
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Returns: A sorted array of log IDs. (newest first)
|
||||
function fetchLogIds() {
|
||||
function fetchLogIds(): Promise<string[]> {
|
||||
// To gather all the log IDs, query for all records in logslastmod.
|
||||
const o = db
|
||||
.transaction("logslastmod", "readonly")
|
||||
.objectStore("logslastmod");
|
||||
return selectQuery(o, undefined, (cursor) => {
|
||||
return selectQuery(o, undefined, (cursor: Cursor) => {
|
||||
return {
|
||||
id: cursor.value.id,
|
||||
ts: cursor.value.ts,
|
||||
id: cursor.id,
|
||||
ts: cursor.ts,
|
||||
};
|
||||
}).then((res) => {
|
||||
// Sort IDs by timestamp (newest first)
|
||||
|
@ -318,14 +348,14 @@ export class IndexedDBLogStore {
|
|||
.map((a) => a.id);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteLogs(id) {
|
||||
function deleteLogs(id: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const txn = db.transaction(["logs", "logslastmod"], "readwrite");
|
||||
const o = txn.objectStore("logs");
|
||||
// only load the key path, not the data which may be huge
|
||||
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
||||
query.onsuccess = (event) => {
|
||||
// @ts-ignore
|
||||
const cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
|
@ -340,6 +370,7 @@ export class IndexedDBLogStore {
|
|||
reject(
|
||||
new Error(
|
||||
"Failed to delete logs for " +
|
||||
// @ts-ignore
|
||||
`'${id}' : ${event.target.errorCode}`
|
||||
)
|
||||
);
|
||||
|
@ -351,11 +382,14 @@ export class IndexedDBLogStore {
|
|||
}
|
||||
|
||||
const allLogIds = await fetchLogIds();
|
||||
let removeLogIds = [];
|
||||
let removeLogIds: string[] = [];
|
||||
const logs = [];
|
||||
let size = 0;
|
||||
for (let i = 0; i < allLogIds.length; i++) {
|
||||
const lines = await fetchLogs(allLogIds[i], MAX_LOG_SIZE - size);
|
||||
const lines: string[] = await fetchLogs(
|
||||
allLogIds[i],
|
||||
MAX_LOG_SIZE - size
|
||||
);
|
||||
|
||||
// always add the log file: fetchLogs will truncate once the maxSize we give it is
|
||||
// exceeded, so we'll go over the max but only by one fragment's worth.
|
||||
|
@ -375,22 +409,22 @@ export class IndexedDBLogStore {
|
|||
}
|
||||
}
|
||||
if (removeLogIds.length > 0) {
|
||||
logger.log("Removing logs: ", removeLogIds);
|
||||
this.logger.log("Removing logs: ", removeLogIds);
|
||||
// Don't await this because it's non-fatal if we can't clean up
|
||||
// logs.
|
||||
Promise.all(removeLogIds.map((id) => deleteLogs(id))).then(
|
||||
() => {
|
||||
logger.log(`Removed ${removeLogIds.length} old logs.`);
|
||||
this.logger.log(`Removed ${removeLogIds.length} old logs.`);
|
||||
},
|
||||
(err) => {
|
||||
logger.error(err);
|
||||
this.logger.error(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
return logs;
|
||||
}
|
||||
|
||||
generateLogEntry(lines) {
|
||||
generateLogEntry(lines: string[]): LogEntry {
|
||||
return {
|
||||
id: this.id,
|
||||
lines: lines,
|
||||
|
@ -416,18 +450,22 @@ export class IndexedDBLogStore {
|
|||
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
|
||||
* resultMapper.
|
||||
*/
|
||||
function selectQuery(store, keyRange, resultMapper) {
|
||||
function selectQuery(
|
||||
store: IDBObjectStore,
|
||||
keyRange: IDBKeyRange,
|
||||
resultMapper: (arg: Cursor) => Cursor
|
||||
): Promise<Cursor[]> {
|
||||
const query = store.openCursor(keyRange);
|
||||
return new Promise((resolve, reject) => {
|
||||
const results = [];
|
||||
query.onerror = (event) => {
|
||||
const results: Cursor[] = [];
|
||||
query.onerror = (event: Event) => {
|
||||
// @ts-ignore
|
||||
reject(new Error("Query failed: " + event.target.errorCode));
|
||||
};
|
||||
// collect results
|
||||
query.onsuccess = (event) => {
|
||||
query.onsuccess = (event: Event) => {
|
||||
// @ts-ignore
|
||||
const cursor = event.target.result;
|
||||
const cursor = event.target.result?.value;
|
||||
if (!cursor) {
|
||||
resolve(results);
|
||||
return; // end of results
|
||||
|
@ -437,6 +475,16 @@ function selectQuery(store, keyRange, resultMapper) {
|
|||
};
|
||||
});
|
||||
}
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_store: IndexedDBLogStore;
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_logger: ConsoleLogger;
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_initPromise: Promise<void>;
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_initStoragePromise: Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure rage shaking support for sending bug reports.
|
||||
|
@ -445,7 +493,7 @@ function selectQuery(store, keyRange, resultMapper) {
|
|||
* be set up immediately for the logs.
|
||||
* @return {Promise} Resolves when set up.
|
||||
*/
|
||||
export function init(setUpPersistence = true) {
|
||||
export function init(setUpPersistence = true): Promise<void> {
|
||||
if (global.mx_rage_initPromise) {
|
||||
return global.mx_rage_initPromise;
|
||||
}
|
||||
|
@ -465,7 +513,7 @@ export function init(setUpPersistence = true) {
|
|||
* then this no-ops.
|
||||
* @return {Promise} Resolves when complete.
|
||||
*/
|
||||
export function tryInitStorage() {
|
||||
export function tryInitStorage(): Promise<void> {
|
||||
if (global.mx_rage_initStoragePromise) {
|
||||
return global.mx_rage_initStoragePromise;
|
||||
}
|
|
@ -15,14 +15,30 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import pako from "pako";
|
||||
import { ClientEvent, MatrixClient, MatrixEvent } from "matrix-js-sdk";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { InspectorContext } from "../room/GroupCallInspector";
|
||||
import { useModalTriggerState } from "../Modal";
|
||||
|
||||
export function useSubmitRageshake() {
|
||||
const { client } = useClient();
|
||||
interface RageShakeSubmitOptions {
|
||||
description: string;
|
||||
roomId: string;
|
||||
label: string;
|
||||
sendLogs: boolean;
|
||||
rageshakeRequestId: string;
|
||||
}
|
||||
|
||||
export function useSubmitRageshake(): {
|
||||
submitRageshake: (opts: RageShakeSubmitOptions) => Promise<void>;
|
||||
sending: boolean;
|
||||
sent: boolean;
|
||||
error: Error;
|
||||
} {
|
||||
const client: MatrixClient = useClient().client;
|
||||
const [{ json }] = useContext(InspectorContext);
|
||||
|
||||
const [{ sending, sent, error }, setState] = useState({
|
||||
|
@ -57,9 +73,12 @@ export function useSubmitRageshake() {
|
|||
opts.description || "User did not supply any additional text."
|
||||
);
|
||||
body.append("app", "matrix-video-chat");
|
||||
body.append("version", import.meta.env.VITE_APP_VERSION || "dev");
|
||||
body.append(
|
||||
"version",
|
||||
(import.meta.env.VITE_APP_VERSION as string) || "dev"
|
||||
);
|
||||
body.append("user_agent", userAgent);
|
||||
body.append("installed_pwa", false);
|
||||
body.append("installed_pwa", "false");
|
||||
body.append("touch_input", touchInput);
|
||||
|
||||
if (client) {
|
||||
|
@ -181,7 +200,11 @@ export function useSubmitRageshake() {
|
|||
|
||||
if (navigator.storage && navigator.storage.estimate) {
|
||||
try {
|
||||
const estimate = await navigator.storage.estimate();
|
||||
const estimate: {
|
||||
quota?: number;
|
||||
usage?: number;
|
||||
usageDetails?: { [x: string]: unknown };
|
||||
} = await navigator.storage.estimate();
|
||||
body.append("storageManager_quota", String(estimate.quota));
|
||||
body.append("storageManager_usage", String(estimate.usage));
|
||||
if (estimate.usageDetails) {
|
||||
|
@ -200,7 +223,11 @@ export function useSubmitRageshake() {
|
|||
|
||||
for (const entry of logs) {
|
||||
// encode as UTF-8
|
||||
let buf = new TextEncoder().encode(entry.lines);
|
||||
let buf = new TextEncoder().encode(
|
||||
typeof entry.lines == "string"
|
||||
? entry.lines
|
||||
: entry.lines.join("\n")
|
||||
);
|
||||
|
||||
// compress
|
||||
buf = pako.gzip(buf);
|
||||
|
@ -225,7 +252,7 @@ export function useSubmitRageshake() {
|
|||
}
|
||||
|
||||
await fetch(
|
||||
import.meta.env.VITE_RAGESHAKE_SUBMIT_URL ||
|
||||
(import.meta.env.VITE_RAGESHAKE_SUBMIT_URL as string) ||
|
||||
"https://element.io/bugreports/submit",
|
||||
{
|
||||
method: "POST",
|
||||
|
@ -250,7 +277,7 @@ export function useSubmitRageshake() {
|
|||
};
|
||||
}
|
||||
|
||||
export function useDownloadDebugLog() {
|
||||
export function useDownloadDebugLog(): () => void {
|
||||
const [{ json }] = useContext(InspectorContext);
|
||||
|
||||
const downloadDebugLog = useCallback(() => {
|
||||
|
@ -271,7 +298,10 @@ export function useDownloadDebugLog() {
|
|||
return downloadDebugLog;
|
||||
}
|
||||
|
||||
export function useRageshakeRequest() {
|
||||
export function useRageshakeRequest(): (
|
||||
roomId: string,
|
||||
rageshakeRequestId: string
|
||||
) => void {
|
||||
const { client } = useClient();
|
||||
|
||||
const sendRageshakeRequest = useCallback(
|
||||
|
@ -286,13 +316,16 @@ export function useRageshakeRequest() {
|
|||
return sendRageshakeRequest;
|
||||
}
|
||||
|
||||
export function useRageshakeRequestModal(roomId) {
|
||||
export function useRageshakeRequestModal(roomId: string): {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: any;
|
||||
} {
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const { client } = useClient();
|
||||
const client: MatrixClient = useClient().client;
|
||||
const [rageshakeRequestId, setRageshakeRequestId] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
const onEvent = (event) => {
|
||||
const onEvent = (event: MatrixEvent) => {
|
||||
const type = event.getType();
|
||||
|
||||
if (
|
||||
|
@ -305,10 +338,10 @@ export function useRageshakeRequestModal(roomId) {
|
|||
}
|
||||
};
|
||||
|
||||
client.on("event", onEvent);
|
||||
client.on(ClientEvent.Event, onEvent);
|
||||
|
||||
return () => {
|
||||
client.removeListener("event", onEvent);
|
||||
client.removeListener(ClientEvent.Event, onEvent);
|
||||
};
|
||||
}, [modalState.open, roomId, client, modalState]);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/*
|
||||
Copyright 2022 Matrix.org Foundation C.I.C.
|
||||
|
||||
|
@ -14,6 +15,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
|
@ -23,9 +25,27 @@ import React, {
|
|||
createContext,
|
||||
} from "react";
|
||||
|
||||
const MediaHandlerContext = createContext();
|
||||
export interface MediaHandlerContextInterface {
|
||||
audioInput: string;
|
||||
audioInputs: MediaDeviceInfo[];
|
||||
setAudioInput: (deviceId: string) => void;
|
||||
videoInput: string;
|
||||
videoInputs: MediaDeviceInfo[];
|
||||
setVideoInput: (deviceId: string) => void;
|
||||
audioOutput: string;
|
||||
audioOutputs: MediaDeviceInfo[];
|
||||
setAudioOutput: (deviceId: string) => void;
|
||||
}
|
||||
|
||||
function getMediaPreferences() {
|
||||
const MediaHandlerContext =
|
||||
createContext<MediaHandlerContextInterface>(undefined);
|
||||
|
||||
interface MediaPreferences {
|
||||
audioInput?: string;
|
||||
videoInput?: string;
|
||||
audioOutput?: string;
|
||||
}
|
||||
function getMediaPreferences(): MediaPreferences {
|
||||
const mediaPreferences = localStorage.getItem("matrix-media-preferences");
|
||||
|
||||
if (mediaPreferences) {
|
||||
|
@ -39,8 +59,8 @@ function getMediaPreferences() {
|
|||
}
|
||||
}
|
||||
|
||||
function updateMediaPreferences(newPreferences) {
|
||||
const oldPreferences = getMediaPreferences(newPreferences);
|
||||
function updateMediaPreferences(newPreferences: MediaPreferences): void {
|
||||
const oldPreferences = getMediaPreferences();
|
||||
|
||||
localStorage.setItem(
|
||||
"matrix-media-preferences",
|
||||
|
@ -50,8 +70,11 @@ function updateMediaPreferences(newPreferences) {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function MediaHandlerProvider({ client, children }) {
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
children: JSX.Element[];
|
||||
}
|
||||
export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
|
||||
const [
|
||||
{
|
||||
audioInput,
|
||||
|
@ -72,7 +95,9 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
);
|
||||
|
||||
return {
|
||||
// @ts-ignore
|
||||
audioInput: mediaHandler.audioInput,
|
||||
// @ts-ignore
|
||||
videoInput: mediaHandler.videoInput,
|
||||
audioOutput: undefined,
|
||||
audioInputs: [],
|
||||
|
@ -84,7 +109,7 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
useEffect(() => {
|
||||
const mediaHandler = client.getMediaHandler();
|
||||
|
||||
function updateDevices() {
|
||||
function updateDevices(): void {
|
||||
navigator.mediaDevices.enumerateDevices().then((devices) => {
|
||||
const mediaPreferences = getMediaPreferences();
|
||||
|
||||
|
@ -92,9 +117,10 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
(device) => device.kind === "audioinput"
|
||||
);
|
||||
const audioConnected = audioInputs.some(
|
||||
// @ts-ignore
|
||||
(device) => device.deviceId === mediaHandler.audioInput
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
let audioInput = mediaHandler.audioInput;
|
||||
|
||||
if (!audioConnected && audioInputs.length > 0) {
|
||||
|
@ -105,9 +131,11 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
(device) => device.kind === "videoinput"
|
||||
);
|
||||
const videoConnected = videoInputs.some(
|
||||
// @ts-ignore
|
||||
(device) => device.deviceId === mediaHandler.videoInput
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
let videoInput = mediaHandler.videoInput;
|
||||
|
||||
if (!videoConnected && videoInputs.length > 0) {
|
||||
|
@ -129,7 +157,9 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
}
|
||||
|
||||
if (
|
||||
// @ts-ignore
|
||||
mediaHandler.videoInput !== videoInput ||
|
||||
// @ts-ignore
|
||||
mediaHandler.audioInput !== audioInput
|
||||
) {
|
||||
mediaHandler.setMediaInputs(audioInput, videoInput);
|
||||
|
@ -159,8 +189,8 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
};
|
||||
}, [client]);
|
||||
|
||||
const setAudioInput = useCallback(
|
||||
(deviceId) => {
|
||||
const setAudioInput: (deviceId: string) => void = useCallback(
|
||||
(deviceId: string) => {
|
||||
updateMediaPreferences({ audioInput: deviceId });
|
||||
setState((prevState) => ({ ...prevState, audioInput: deviceId }));
|
||||
client.getMediaHandler().setAudioInput(deviceId);
|
||||
|
@ -168,7 +198,7 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
[client]
|
||||
);
|
||||
|
||||
const setVideoInput = useCallback(
|
||||
const setVideoInput: (deviceId: string) => void = useCallback(
|
||||
(deviceId) => {
|
||||
updateMediaPreferences({ videoInput: deviceId });
|
||||
setState((prevState) => ({ ...prevState, videoInput: deviceId }));
|
||||
|
@ -177,35 +207,36 @@ export function MediaHandlerProvider({ client, children }) {
|
|||
[client]
|
||||
);
|
||||
|
||||
const setAudioOutput = useCallback((deviceId) => {
|
||||
const setAudioOutput: (deviceId: any) => void = useCallback((deviceId) => {
|
||||
updateMediaPreferences({ audioOutput: deviceId });
|
||||
setState((prevState) => ({ ...prevState, audioOutput: deviceId }));
|
||||
}, []);
|
||||
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
videoInput,
|
||||
videoInputs,
|
||||
setVideoInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
}),
|
||||
[
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
videoInput,
|
||||
videoInputs,
|
||||
setVideoInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
]
|
||||
);
|
||||
const context: MediaHandlerContextInterface =
|
||||
useMemo<MediaHandlerContextInterface>(
|
||||
() => ({
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
videoInput,
|
||||
videoInputs,
|
||||
setVideoInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
}),
|
||||
[
|
||||
audioInput,
|
||||
audioInputs,
|
||||
setAudioInput,
|
||||
videoInput,
|
||||
videoInputs,
|
||||
setVideoInput,
|
||||
audioOutput,
|
||||
audioOutputs,
|
||||
setAudioOutput,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<MediaHandlerContext.Provider value={context}>
|
Loading…
Reference in a new issue