Merge remote-tracking branch 'upstream/main' into feature_sfu
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
commit
3d0906a145
16 changed files with 315 additions and 148 deletions
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
Full mesh group calls powered by [Matrix](https://matrix.org), implementing [MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/blob/matthew/group-voip/proposals/3401-group-voip.md).
|
Full mesh group calls powered by [Matrix](https://matrix.org), implementing [MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/blob/matthew/group-voip/proposals/3401-group-voip.md).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
To try it out, visit our hosted version at [call.element.io](https://call.element.io). You can also find the latest development version continuously deployed to [element-call.netlify.app](https://element-call.netlify.app).
|
To try it out, visit our hosted version at [call.element.io](https://call.element.io). You can also find the latest development version continuously deployed to [element-call.netlify.app](https://element-call.netlify.app).
|
||||||
|
|
||||||
## Host it yourself
|
## Host it yourself
|
||||||
|
|
BIN
demo.jpg
Normal file
BIN
demo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
|
@ -45,7 +45,7 @@
|
||||||
"i18next": "^21.10.0",
|
"i18next": "^21.10.0",
|
||||||
"i18next-browser-languagedetector": "^6.1.8",
|
"i18next-browser-languagedetector": "^6.1.8",
|
||||||
"i18next-http-backend": "^1.4.4",
|
"i18next-http-backend": "^1.4.4",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#278dd6a3d35cf89c03f9172f9c81579577a267b3",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#ac97928a5d081b0502952295eea5b8fa7bdf8839",
|
||||||
"matrix-widget-api": "^1.0.0",
|
"matrix-widget-api": "^1.0.0",
|
||||||
"mermaid": "^8.13.8",
|
"mermaid": "^8.13.8",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
|
|
@ -136,5 +136,8 @@
|
||||||
"This will send anonymised data (such as the duration of a call and the number of participants) to the Element Call team to help us optimise the application based on how it is used.": "Esto enviará datos anónimos (como la duración de la llamada y el número de participantes) al equipo de Element Call para ayudarnos a optimizar la aplicación dependiendo de cómo se use.",
|
"This will send anonymised data (such as the duration of a call and the number of participants) to the Element Call team to help us optimise the application based on how it is used.": "Esto enviará datos anónimos (como la duración de la llamada y el número de participantes) al equipo de Element Call para ayudarnos a optimizar la aplicación dependiendo de cómo se use.",
|
||||||
"Whether to enable single-key keyboard shortcuts, e.g. 'm' to mute/unmute the mic.": "Habilita los atajos de teclado de una sola tecla, por ejemplo 'm' para silenciar/desilenciar el micrófono.",
|
"Whether to enable single-key keyboard shortcuts, e.g. 'm' to mute/unmute the mic.": "Habilita los atajos de teclado de una sola tecla, por ejemplo 'm' para silenciar/desilenciar el micrófono.",
|
||||||
"Single-key keyboard shortcuts": "Atajos de teclado de una sola tecla",
|
"Single-key keyboard shortcuts": "Atajos de teclado de una sola tecla",
|
||||||
"{{name}} (Waiting for video...)": "{{name}} (Esperando al video...)"
|
"{{name}} (Waiting for video...)": "{{name}} (Esperando al video...)",
|
||||||
|
"This feature is only supported on Firefox.": "Esta característica solo está disponible en Firefox.",
|
||||||
|
"<0>Submitting debug logs will help us track down the problem.</0>": "<0>Subir los registros de depuración nos ayudará a encontrar el problema.</0>",
|
||||||
|
"<0>Oops, something's gone wrong.</0>": "<0>Ups, algo ha salido mal.</0>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,95 @@
|
||||||
{}
|
{
|
||||||
|
"{{name}} (Connecting...)": "{{name}}(接続しています…)",
|
||||||
|
"{{count}} people connected|other": "{{count}}人が接続済",
|
||||||
|
"{{count}} people connected|one": "{{count}}人が接続済",
|
||||||
|
"{{name}} (Waiting for video...)": "{{name}}(ビデオを待機しています…)",
|
||||||
|
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>既にアカウントをお持ちですか?</0><1><0>ログイン</0>または<2>ゲストとしてアクセス</2></1>",
|
||||||
|
"{{roomName}} - Walkie-talkie call": "{{roomName}} - トランシーバー通話",
|
||||||
|
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>アカウントを作成</0>または<2>ゲストとしてアクセス</2>",
|
||||||
|
"<0>Join call now</0><1>Or</1><2>Copy call link and join later</2>": "<0>今すぐ通話に参加</0><1>または</1><2>通話リンクをコピーし、後で参加</2>",
|
||||||
|
"Accept camera/microphone permissions to join the call.": "通話に参加するには、カメラ・マイクの許可が必要です。",
|
||||||
|
"<0>Oops, something's gone wrong.</0>": "<0>何かがうまく行きませんでした。</0>",
|
||||||
|
"Camera/microphone permissions needed to join the call.": "通話に参加する場合、カメラ・マイクの許可が必要です。",
|
||||||
|
"Allow analytics": "アナリティクスを許可",
|
||||||
|
"Camera": "カメラ",
|
||||||
|
"Call link copied": "通話リンクをコピーしました",
|
||||||
|
"By clicking \"Join call now\", you agree to our <2>Terms and conditions</2>": "「今すぐ通話に参加」をクリックすると、<2>利用規約</2>に同意したとみなされます",
|
||||||
|
"By clicking \"Go\", you agree to our <2>Terms and conditions</2>": "「続行」をクリックすると、 <2>利用規約</2>に同意したとみなされます",
|
||||||
|
"Avatar": "アバター",
|
||||||
|
"Accept microphone permissions to join the call.": "通話に参加するには、マイクの許可が必要です。",
|
||||||
|
"Audio": "音声",
|
||||||
|
"Advanced": "高度",
|
||||||
|
"Connection lost": "接続が切断されました",
|
||||||
|
"Confirm password": "パスワードを確認",
|
||||||
|
"Close": "閉じる",
|
||||||
|
"Change layout": "レイアウトを変更",
|
||||||
|
"Copied!": "コピーしました!",
|
||||||
|
"Copy and share this call link": "通話リンクをコピーし共有",
|
||||||
|
"Copy": "コピー",
|
||||||
|
"Description (optional)": "概要(任意)",
|
||||||
|
"Debug log": "デバッグログ",
|
||||||
|
"Create account": "アカウントを作成",
|
||||||
|
"Having trouble? Help us fix it.": "問題が起きましたか?修正にご協力ください。",
|
||||||
|
"Go": "続行",
|
||||||
|
"Fetching group call timed out.": "グループ通話の取得がタイムアウトしました。",
|
||||||
|
"Element Call Home": "Element Call ホーム",
|
||||||
|
"Download debug logs": "デバッグログをダウンロード",
|
||||||
|
"Display name": "表示名",
|
||||||
|
"Developer": "開発者",
|
||||||
|
"Details": "詳細",
|
||||||
|
"Full screen": "全画面表示",
|
||||||
|
"Exit full screen": "全画面表示を終了",
|
||||||
|
"Include debug logs": "デバッグログを含める",
|
||||||
|
"Home": "ホーム",
|
||||||
|
"Incompatible versions!": "互換性のないバージョンです!",
|
||||||
|
"Incompatible versions": "互換性のないバージョン",
|
||||||
|
"Join existing call?": "既存の通話に参加しますか?",
|
||||||
|
"Join call now": "今すぐ通話に参加",
|
||||||
|
"Join call": "通話に参加",
|
||||||
|
"Invite": "招待",
|
||||||
|
"Invite people": "連絡先を招待",
|
||||||
|
"Not registered yet? <2>Create an account</2>": "アカウントがありませんか? <2>アカウントを作成</2>",
|
||||||
|
"Mute microphone": "マイクをミュート",
|
||||||
|
"Microphone permissions needed to join the call.": "通話の参加にはマイクの許可が必要です。",
|
||||||
|
"Microphone": "マイク",
|
||||||
|
"Login": "ログイン",
|
||||||
|
"Logging in…": "ログインしています…",
|
||||||
|
"Loading…": "読み込んでいます…",
|
||||||
|
"Loading room…": "ルームを読み込んでいます…",
|
||||||
|
"Leave": "退出",
|
||||||
|
"Version: {{version}}": "バージョン:{{version}}",
|
||||||
|
"Username": "ユーザー名",
|
||||||
|
"User menu": "ユーザーメニュー",
|
||||||
|
"User ID": "ユーザーID",
|
||||||
|
"Unmute microphone": "マイクのミュートを解除",
|
||||||
|
"Turn on camera": "カメラをつける",
|
||||||
|
"Turn off camera": "カメラを切る",
|
||||||
|
"Submitting feedback…": "フィードバックを送信しています…",
|
||||||
|
"Submit feedback": "フィードバックを送信",
|
||||||
|
"Stop sharing screen": "画面共有を停止",
|
||||||
|
"Spotlight": "スポットライト",
|
||||||
|
"Send debug logs": "デバッグログを送信",
|
||||||
|
"Sign out": "サインアウト",
|
||||||
|
"Sign in": "サインイン",
|
||||||
|
"Share screen": "画面共有",
|
||||||
|
"Settings": "設定",
|
||||||
|
"Sending…": "送信しています…",
|
||||||
|
"Sending debug logs…": "デバッグログを送信しています…",
|
||||||
|
"Saving…": "保存しています…",
|
||||||
|
"Save": "保存",
|
||||||
|
"Return to home screen": "ホーム画面に戻る",
|
||||||
|
"Registering…": "登録しています…",
|
||||||
|
"Register": "登録",
|
||||||
|
"Profile": "プロフィール",
|
||||||
|
"Press and hold spacebar to talk": "スペースを長押しで会話",
|
||||||
|
"Passwords must match": "パスワードが一致する必要があります",
|
||||||
|
"Password": "パスワード",
|
||||||
|
"Speaker": "スピーカー",
|
||||||
|
"Video call name": "ビデオ通話の名称",
|
||||||
|
"Video call": "ビデオ通話",
|
||||||
|
"Video": "ビデオ",
|
||||||
|
"Waiting for other participants…": "他の参加者を待機しています…",
|
||||||
|
"Waiting for network": "ネットワークを待機しています",
|
||||||
|
"Walkie-talkie call name": "トランシーバー通話の名前",
|
||||||
|
"Walkie-talkie call": "トランシーバー通話"
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
SignupTracker,
|
SignupTracker,
|
||||||
MuteCameraTracker,
|
MuteCameraTracker,
|
||||||
MuteMicrophoneTracker,
|
MuteMicrophoneTracker,
|
||||||
|
UndecryptableToDeviceEventTracker,
|
||||||
} from "./PosthogEvents";
|
} from "./PosthogEvents";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
import { getUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
|
@ -415,4 +416,5 @@ export class PosthogAnalytics {
|
||||||
public eventLogin = new LoginTracker();
|
public eventLogin = new LoginTracker();
|
||||||
public eventMuteMicrophone = new MuteMicrophoneTracker();
|
public eventMuteMicrophone = new MuteMicrophoneTracker();
|
||||||
public eventMuteCamera = new MuteCameraTracker();
|
public eventMuteCamera = new MuteCameraTracker();
|
||||||
|
public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker();
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,3 +149,17 @@ export class MuteCameraTracker {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UndecryptableToDeviceEvent {
|
||||||
|
eventName: "UndecryptableToDeviceEvent";
|
||||||
|
callId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UndecryptableToDeviceEventTracker {
|
||||||
|
track(callId: string) {
|
||||||
|
PosthogAnalytics.instance.trackEvent<UndecryptableToDeviceEvent>({
|
||||||
|
eventName: "UndecryptableToDeviceEvent",
|
||||||
|
callId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -216,6 +216,12 @@ export function fullAliasFromRoomName(
|
||||||
return `#${roomAliasLocalpartFromRoomName(roomName)}:${client.getDomain()}`;
|
return `#${roomAliasLocalpartFromRoomName(roomName)}:${client.getDomain()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XXX: What is this trying to do? It looks like it's getting the localpart from
|
||||||
|
* a room alias, but why is it splitting on hyphens and then putting spaces in??
|
||||||
|
* @param roomId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function roomNameFromRoomId(roomId: string): string {
|
export function roomNameFromRoomId(roomId: string): string {
|
||||||
return roomId
|
return roomId
|
||||||
.match(/([^:]+):.*$/)[1]
|
.match(/([^:]+):.*$/)[1]
|
||||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
import { Resizable } from "re-resizable";
|
import { Resizable } from "re-resizable";
|
||||||
import React, {
|
import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -34,6 +35,7 @@ import { CallEvent } from "matrix-js-sdk/src/webrtc/call";
|
||||||
|
|
||||||
import styles from "./GroupCallInspector.module.css";
|
import styles from "./GroupCallInspector.module.css";
|
||||||
import { SelectInput } from "../input/SelectInput";
|
import { SelectInput } from "../input/SelectInput";
|
||||||
|
import { PosthogAnalytics } from "../PosthogAnalytics";
|
||||||
|
|
||||||
interface InspectorContextState {
|
interface InspectorContextState {
|
||||||
eventsByUserId?: { [userId: string]: SequenceDiagramMatrixEvent[] };
|
eventsByUserId?: { [userId: string]: SequenceDiagramMatrixEvent[] };
|
||||||
|
@ -108,6 +110,19 @@ function formatTimestamp(timestamp: number | Date) {
|
||||||
return dateFormatter.format(timestamp);
|
return dateFormatter.format(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatType(event: SequenceDiagramMatrixEvent): string {
|
||||||
|
if (event.content.msgtype === "m.bad.encrypted") return "Undecryptable";
|
||||||
|
return event.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineForEvent(event: SequenceDiagramMatrixEvent): string {
|
||||||
|
return `${getUserName(event.from)} ${
|
||||||
|
event.ignored ? "-x" : "->>"
|
||||||
|
} ${getUserName(event.to)}: ${formatTimestamp(event.timestamp)} ${formatType(
|
||||||
|
event
|
||||||
|
)} ${formatContent(event.type, event.content)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export const InspectorContext =
|
export const InspectorContext =
|
||||||
createContext<
|
createContext<
|
||||||
[
|
[
|
||||||
|
@ -187,21 +202,7 @@ export function SequenceDiagramViewer({
|
||||||
participant ${getUserName(localUserId)}
|
participant ${getUserName(localUserId)}
|
||||||
participant Room
|
participant Room
|
||||||
participant ${selectedUserId ? getUserName(selectedUserId) : "unknown"}
|
participant ${selectedUserId ? getUserName(selectedUserId) : "unknown"}
|
||||||
${
|
${events ? events.map(lineForEvent).join("\n ") : ""}
|
||||||
events
|
|
||||||
? events
|
|
||||||
.map(
|
|
||||||
({ to, from, timestamp, type, content, ignored }) =>
|
|
||||||
`${getUserName(from)} ${ignored ? "-x" : "->>"} ${getUserName(
|
|
||||||
to
|
|
||||||
)}: ${formatTimestamp(timestamp)} ${type} ${formatContent(
|
|
||||||
type,
|
|
||||||
content
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
.join("\n ")
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
mermaid.mermaidAPI.render("mermaid", graphDefinition, (svgCode: string) => {
|
mermaid.mermaidAPI.render("mermaid", graphDefinition, (svgCode: string) => {
|
||||||
|
@ -389,12 +390,23 @@ function useGroupCallState(
|
||||||
function onSendVoipEvent(event: Record<string, unknown>) {
|
function onSendVoipEvent(event: Record<string, unknown>) {
|
||||||
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
|
dispatch({ type: CallEvent.SendVoipEvent, rawEvent: event });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUndecryptableToDevice(event: MatrixEvent) {
|
||||||
|
dispatch({ type: ClientEvent.ReceivedVoipEvent, event });
|
||||||
|
|
||||||
|
Sentry.captureMessage("Undecryptable to-device Event");
|
||||||
|
PosthogAnalytics.instance.eventUndecryptableToDevice.track(
|
||||||
|
groupCall.groupCallId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
client.on(RoomStateEvent.Events, onUpdateRoomState);
|
client.on(RoomStateEvent.Events, onUpdateRoomState);
|
||||||
//groupCall.on("calls_changed", onCallsChanged);
|
//groupCall.on("calls_changed", onCallsChanged);
|
||||||
groupCall.on(CallEvent.SendVoipEvent, onSendVoipEvent);
|
groupCall.on(CallEvent.SendVoipEvent, onSendVoipEvent);
|
||||||
//client.on("state", onCallsChanged);
|
//client.on("state", onCallsChanged);
|
||||||
//client.on("hangup", onCallHangup);
|
//client.on("hangup", onCallHangup);
|
||||||
client.on(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
|
client.on(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
|
||||||
|
client.on(ClientEvent.UndecryptableToDeviceEvent, onUndecryptableToDevice);
|
||||||
|
|
||||||
onUpdateRoomState();
|
onUpdateRoomState();
|
||||||
|
|
||||||
|
@ -405,6 +417,10 @@ function useGroupCallState(
|
||||||
//client.removeListener("state", onCallsChanged);
|
//client.removeListener("state", onCallsChanged);
|
||||||
//client.removeListener("hangup", onCallHangup);
|
//client.removeListener("hangup", onCallHangup);
|
||||||
client.removeListener(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
|
client.removeListener(ClientEvent.ReceivedVoipEvent, onReceivedVoipEvent);
|
||||||
|
client.removeListener(
|
||||||
|
ClientEvent.UndecryptableToDeviceEvent,
|
||||||
|
onUndecryptableToDevice
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [client, groupCall]);
|
}, [client, groupCall]);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React, { useCallback, useState, useRef } from "react";
|
import React, { useCallback, useState, useRef } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useSpring, animated } from "@react-spring/web";
|
import { useSpring, animated } from "@react-spring/web";
|
||||||
|
import { logger } from "@sentry/utils";
|
||||||
|
|
||||||
import styles from "./PTTButton.module.css";
|
import styles from "./PTTButton.module.css";
|
||||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
||||||
|
@ -68,11 +69,23 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
enqueueNetworkWaiting(true, 100);
|
enqueueNetworkWaiting(true, 100);
|
||||||
startTalking();
|
startTalking();
|
||||||
}, [enqueueNetworkWaiting, startTalking, buttonHeld]);
|
}, [enqueueNetworkWaiting, startTalking, buttonHeld]);
|
||||||
|
|
||||||
const unhold = useCallback(() => {
|
const unhold = useCallback(() => {
|
||||||
|
if (!buttonHeld) return;
|
||||||
setButtonHeld(false);
|
setButtonHeld(false);
|
||||||
setNetworkWaiting(false);
|
setNetworkWaiting(false);
|
||||||
stopTalking();
|
stopTalking();
|
||||||
}, [setNetworkWaiting, stopTalking]);
|
}, [setNetworkWaiting, stopTalking, buttonHeld]);
|
||||||
|
|
||||||
|
const onMouseUp = useCallback(() => {
|
||||||
|
logger.info("Mouse up event: unholding PTT button");
|
||||||
|
unhold();
|
||||||
|
}, [unhold]);
|
||||||
|
|
||||||
|
const onBlur = useCallback(() => {
|
||||||
|
logger.info("Blur event: unholding PTT button");
|
||||||
|
unhold();
|
||||||
|
}, [unhold]);
|
||||||
|
|
||||||
const onButtonMouseDown = useCallback(
|
const onButtonMouseDown = useCallback(
|
||||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
@ -85,7 +98,7 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
// These listeners go on the window so even if the user's cursor / finger
|
// These listeners go on the window so even if the user's cursor / finger
|
||||||
// leaves the button while holding it, the button stays pushed until
|
// leaves the button while holding it, the button stays pushed until
|
||||||
// they stop clicking / tapping.
|
// they stop clicking / tapping.
|
||||||
useEventTarget(window, "mouseup", unhold);
|
useEventTarget(window, "mouseup", onMouseUp);
|
||||||
useEventTarget(
|
useEventTarget(
|
||||||
window,
|
window,
|
||||||
"touchend",
|
"touchend",
|
||||||
|
@ -103,6 +116,8 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
if (!touchFound) return;
|
if (!touchFound) return;
|
||||||
|
|
||||||
|
logger.info("Touch event ended: unholding PTT button");
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
unhold();
|
unhold();
|
||||||
setActiveTouchId(null);
|
setActiveTouchId(null);
|
||||||
|
@ -163,6 +178,8 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
logger.info("Keyup event for spacebar: unholding PTT button");
|
||||||
|
|
||||||
unhold();
|
unhold();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -171,7 +188,7 @@ export const PTTButton: React.FC<Props> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: We will need to disable this for a global PTT hotkey to work
|
// TODO: We will need to disable this for a global PTT hotkey to work
|
||||||
useEventTarget(window, "blur", unhold);
|
useEventTarget(window, "blur", onBlur);
|
||||||
|
|
||||||
const prefersReducedMotion = usePrefersReducedMotion();
|
const prefersReducedMotion = usePrefersReducedMotion();
|
||||||
const { shadow } = useSpring({
|
const { shadow } = useSpring({
|
||||||
|
|
|
@ -210,36 +210,36 @@ export const PTTCallView: React.FC<Props> = ({
|
||||||
</Header>
|
</Header>
|
||||||
)}
|
)}
|
||||||
<div className={styles.center}>
|
<div className={styles.center}>
|
||||||
{showControls && (
|
{/* Always render this because the window will become shorter when the on-screen
|
||||||
<>
|
keyboard appears, so if we don't render it, the dialog will unmount. */}
|
||||||
<div className={styles.participants}>
|
<div style={{ display: showControls ? "block" : "none" }}>
|
||||||
<p>
|
<div className={styles.participants}>
|
||||||
{t("{{count}} people connected", {
|
<p>
|
||||||
count: participatingMembers.length,
|
{t("{{count}} people connected", {
|
||||||
})}
|
count: participatingMembers.length,
|
||||||
</p>
|
})}
|
||||||
<Facepile
|
</p>
|
||||||
size={facepileSize}
|
<Facepile
|
||||||
max={8}
|
size={facepileSize}
|
||||||
className={styles.facepile}
|
max={8}
|
||||||
client={client}
|
className={styles.facepile}
|
||||||
members={participatingMembers}
|
client={client}
|
||||||
/>
|
members={participatingMembers}
|
||||||
</div>
|
/>
|
||||||
<div className={styles.footer}>
|
</div>
|
||||||
<OverflowMenu
|
<div className={styles.footer}>
|
||||||
inCall
|
<OverflowMenu
|
||||||
roomIdOrAlias={roomIdOrAlias}
|
inCall
|
||||||
groupCall={groupCall}
|
roomIdOrAlias={roomIdOrAlias}
|
||||||
showInvite={false}
|
groupCall={groupCall}
|
||||||
feedbackModalState={feedbackModalState}
|
showInvite={false}
|
||||||
feedbackModalProps={feedbackModalProps}
|
feedbackModalState={feedbackModalState}
|
||||||
/>
|
feedbackModalProps={feedbackModalProps}
|
||||||
{!isEmbedded && <HangupButton onPress={onLeave} />}
|
/>
|
||||||
<InviteButton onPress={() => inviteModalState.open()} />
|
{!isEmbedded && <HangupButton onPress={onLeave} />}
|
||||||
</div>
|
<InviteButton onPress={() => inviteModalState.open()} />
|
||||||
</>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
<div className={styles.pttButtonContainer}>
|
<div className={styles.pttButtonContainer}>
|
||||||
{showControls &&
|
{showControls &&
|
||||||
|
|
|
@ -50,9 +50,9 @@ export const RoomPage: FC = () => {
|
||||||
const [isRegistering, setIsRegistering] = useState(false);
|
const [isRegistering, setIsRegistering] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If we're not already authed and we've been given a display name as
|
// If we've finished loading, are not already authed and we've been given a display name as
|
||||||
// a URL param, automatically register a passwordless user
|
// a URL param, automatically register a passwordless user
|
||||||
if (!isAuthenticated && displayName) {
|
if (!loading && !isAuthenticated && displayName) {
|
||||||
setIsRegistering(true);
|
setIsRegistering(true);
|
||||||
registerPasswordlessUser(displayName).finally(() => {
|
registerPasswordlessUser(displayName).finally(() => {
|
||||||
setIsRegistering(false);
|
setIsRegistering(false);
|
||||||
|
@ -63,6 +63,7 @@ export const RoomPage: FC = () => {
|
||||||
displayName,
|
displayName,
|
||||||
setIsRegistering,
|
setIsRegistering,
|
||||||
registerPasswordlessUser,
|
registerPasswordlessUser,
|
||||||
|
loading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const groupCallView = useCallback(
|
const groupCallView = useCallback(
|
||||||
|
|
|
@ -52,12 +52,22 @@ export const useLoadGroupCall = (
|
||||||
|
|
||||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||||
try {
|
try {
|
||||||
const room = await client.joinRoom(roomIdOrAlias, { viaServers });
|
// We lowercase the localpart when we create the room, so we must lowercase
|
||||||
|
// it here too (we just do the whole alias). We can't do the same to room IDs
|
||||||
|
// though.
|
||||||
|
const sanitisedIdOrAlias =
|
||||||
|
roomIdOrAlias[0] === "#"
|
||||||
|
? roomIdOrAlias.toLowerCase()
|
||||||
|
: roomIdOrAlias;
|
||||||
|
|
||||||
|
const room = await client.joinRoom(sanitisedIdOrAlias, {
|
||||||
|
viaServers,
|
||||||
|
});
|
||||||
logger.info(
|
logger.info(
|
||||||
`Joined ${roomIdOrAlias}, waiting room to be ready for group calls`
|
`Joined ${sanitisedIdOrAlias}, waiting room to be ready for group calls`
|
||||||
);
|
);
|
||||||
await client.waitUntilRoomReadyForGroupCalls(room.roomId);
|
await client.waitUntilRoomReadyForGroupCalls(room.roomId);
|
||||||
logger.info(`${roomIdOrAlias}, is ready for group calls`);
|
logger.info(`${sanitisedIdOrAlias}, is ready for group calls`);
|
||||||
return room;
|
return room;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -113,12 +113,14 @@ export const usePTT = (
|
||||||
},
|
},
|
||||||
setState,
|
setState,
|
||||||
] = useState(() => {
|
] = useState(() => {
|
||||||
|
// slightly concerningly, this can end up null as we seem to sometimes get
|
||||||
|
// here before the room state contains our own member event
|
||||||
const roomMember = groupCall.room.getMember(client.getUserId());
|
const roomMember = groupCall.room.getMember(client.getUserId());
|
||||||
|
|
||||||
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
const activeSpeakerFeed = getActiveSpeakerFeed(userMediaFeeds, groupCall);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isAdmin: roomMember.powerLevel >= 100,
|
isAdmin: roomMember ? roomMember.powerLevel >= 100 : false,
|
||||||
talkOverEnabled: false,
|
talkOverEnabled: false,
|
||||||
pttButtonHeld: false,
|
pttButtonHeld: false,
|
||||||
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
activeSpeakerUserId: activeSpeakerFeed ? activeSpeakerFeed.userId : null,
|
||||||
|
|
|
@ -1,21 +1,4 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
||||||
/*
|
|
||||||
Copyright 2017 OpenMarket Ltd
|
Copyright 2017 OpenMarket Ltd
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
Copyright 2019 The New Vector Ltd
|
Copyright 2019 The New Vector Ltd
|
||||||
|
@ -54,15 +37,23 @@ limitations under the License.
|
||||||
// actually timestamps. We then purge the remaining logs. We also do this
|
// actually timestamps. We then purge the remaining logs. We also do this
|
||||||
// purge on startup to prevent logs from accumulating.
|
// purge on startup to prevent logs from accumulating.
|
||||||
|
|
||||||
|
import EventEmitter from "events";
|
||||||
|
import { throttle } from "lodash";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
|
||||||
// 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)
|
// the length of log data we keep in indexeddb (and include in the reports)
|
||||||
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
|
const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB
|
||||||
|
|
||||||
|
// Shortest amount of time between flushes. We are just appending to an
|
||||||
|
// IndexedDB table so we don't expect flushing to be that expensive, but
|
||||||
|
// we can batch the writes a little.
|
||||||
|
const MAX_FLUSH_INTERVAL_MS = 2 * 1000;
|
||||||
|
|
||||||
|
enum ConsoleLoggerEvent {
|
||||||
|
Log = "log",
|
||||||
|
}
|
||||||
|
|
||||||
type LogFunction = (
|
type LogFunction = (
|
||||||
...args: (Error | DOMException | object | string)[]
|
...args: (Error | DOMException | object | string)[]
|
||||||
) => void;
|
) => void;
|
||||||
|
@ -76,7 +67,7 @@ interface LogEntry {
|
||||||
index?: number;
|
index?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConsoleLogger {
|
export class ConsoleLogger extends EventEmitter {
|
||||||
private logs = "";
|
private logs = "";
|
||||||
private originalFunctions: { [key in LogFunctionName]?: LogFunction } = {};
|
private originalFunctions: { [key in LogFunctionName]?: LogFunction } = {};
|
||||||
|
|
||||||
|
@ -99,13 +90,6 @@ export class ConsoleLogger {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public bypassRageshake(
|
|
||||||
fnName: LogFunctionName,
|
|
||||||
...args: (Error | DOMException | object | string)[]
|
|
||||||
): void {
|
|
||||||
this.originalFunctions[fnName](...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public log(
|
public log(
|
||||||
level: string,
|
level: string,
|
||||||
...args: (Error | DOMException | object | string)[]
|
...args: (Error | DOMException | object | string)[]
|
||||||
|
@ -137,23 +121,27 @@ export class ConsoleLogger {
|
||||||
// Using + really is the quickest way in JS
|
// Using + really is the quickest way in JS
|
||||||
// http://jsperf.com/concat-vs-plus-vs-join
|
// http://jsperf.com/concat-vs-plus-vs-join
|
||||||
this.logs += line;
|
this.logs += line;
|
||||||
|
|
||||||
|
this.emit(ConsoleLoggerEvent.Log);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve log lines to flush to disk.
|
* Returns the log lines to flush to disk and empties the internal log buffer
|
||||||
* @param {boolean} keepLogs True to not delete logs after flushing.
|
* @return {string} \n delimited log lines
|
||||||
* @return {string} \n delimited log lines to flush.
|
|
||||||
*/
|
*/
|
||||||
public flush(keepLogs?: boolean): string {
|
public popLogs(): string {
|
||||||
// The ConsoleLogger doesn't care how these end up on disk, it just
|
|
||||||
// flushes them to the caller.
|
|
||||||
if (keepLogs) {
|
|
||||||
return this.logs;
|
|
||||||
}
|
|
||||||
const logsToFlush = this.logs;
|
const logsToFlush = this.logs;
|
||||||
this.logs = "";
|
this.logs = "";
|
||||||
return logsToFlush;
|
return logsToFlush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns lines currently in the log buffer without removing them
|
||||||
|
* @return {string} \n delimited log lines
|
||||||
|
*/
|
||||||
|
public peekLogs(): string {
|
||||||
|
return this.logs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A class which stores log lines in an IndexedDB instance.
|
// A class which stores log lines in an IndexedDB instance.
|
||||||
|
@ -164,8 +152,14 @@ export class IndexedDBLogStore {
|
||||||
private flushAgainPromise: Promise<void> = null;
|
private flushAgainPromise: Promise<void> = null;
|
||||||
private id: string;
|
private id: string;
|
||||||
|
|
||||||
constructor(private indexedDB: IDBFactory, private logger: ConsoleLogger) {
|
constructor(
|
||||||
|
private indexedDB: IDBFactory,
|
||||||
|
private loggerInstance: ConsoleLogger
|
||||||
|
) {
|
||||||
this.id = "instance-" + randomString(16);
|
this.id = "instance-" + randomString(16);
|
||||||
|
|
||||||
|
loggerInstance.on(ConsoleLoggerEvent.Log, this.onLoggerLog);
|
||||||
|
window.addEventListener("beforeunload", this.flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,30 +168,31 @@ export class IndexedDBLogStore {
|
||||||
public connect(): Promise<void> {
|
public connect(): Promise<void> {
|
||||||
const req = this.indexedDB.open("logs");
|
const req = this.indexedDB.open("logs");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
req.onsuccess = (event: Event) => {
|
req.onsuccess = () => {
|
||||||
// @ts-ignore
|
this.db = req.result;
|
||||||
this.db = event.target.result;
|
|
||||||
// Periodically flush logs to local storage / indexeddb
|
|
||||||
setInterval(this.flush.bind(this), FLUSH_RATE_MS);
|
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
req.onerror = (event) => {
|
req.onerror = () => {
|
||||||
const err =
|
const err = "Failed to open log database: " + req.error.name;
|
||||||
// @ts-ignore
|
|
||||||
"Failed to open log database: " + event.target.error.name;
|
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
reject(new Error(err));
|
reject(new Error(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
// First time: Setup the object store
|
// First time: Setup the object store
|
||||||
req.onupgradeneeded = (event) => {
|
req.onupgradeneeded = () => {
|
||||||
// @ts-ignore
|
const db = req.result;
|
||||||
const db = event.target.result;
|
// This is the log entries themselves. Each entry is a chunk of
|
||||||
|
// logs (ie multiple lines). 'id' is the instance ID (so logs with
|
||||||
|
// the same instance ID are all from the same session) and 'index'
|
||||||
|
// is a sequence number for the chunk. The log lines live in the
|
||||||
|
// 'lines' key, which is a chunk of many newline-separated log lines.
|
||||||
const logObjStore = db.createObjectStore("logs", {
|
const logObjStore = db.createObjectStore("logs", {
|
||||||
keyPath: ["id", "index"],
|
keyPath: ["id", "index"],
|
||||||
});
|
});
|
||||||
// Keys in the database look like: [ "instance-148938490", 0 ]
|
// Keys in the database look like: [ "instance-148938490", 0 ]
|
||||||
|
// (The instance ID plus the ID of each log chunk).
|
||||||
// Later on we need to query everything based on an instance id.
|
// Later on we need to query everything based on an instance id.
|
||||||
// In order to do this, we need to set up indexes "id".
|
// In order to do this, we need to set up indexes "id".
|
||||||
logObjStore.createIndex("id", "id", { unique: false });
|
logObjStore.createIndex("id", "id", { unique: false });
|
||||||
|
@ -206,6 +201,9 @@ export class IndexedDBLogStore {
|
||||||
this.generateLogEntry(new Date() + " ::: Log database was created.")
|
this.generateLogEntry(new Date() + " ::: Log database was created.")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// This records the last time each instance ID generated a log message, such
|
||||||
|
// that the logs from each session can be collated in the order they last logged
|
||||||
|
// something.
|
||||||
const lastModifiedStore = db.createObjectStore("logslastmod", {
|
const lastModifiedStore = db.createObjectStore("logslastmod", {
|
||||||
keyPath: "id",
|
keyPath: "id",
|
||||||
});
|
});
|
||||||
|
@ -214,6 +212,26 @@ export class IndexedDBLogStore {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onLoggerLog = () => {
|
||||||
|
if (!this.db) return;
|
||||||
|
|
||||||
|
this.throttledFlush();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Throttled function to flush logs. We use throttle rather
|
||||||
|
// than debounce as we want logs to be written regularly, otherwise
|
||||||
|
// if there's a constant stream of logging, we'd never write anything.
|
||||||
|
private throttledFlush = throttle(
|
||||||
|
() => {
|
||||||
|
this.flush();
|
||||||
|
},
|
||||||
|
MAX_FLUSH_INTERVAL_MS,
|
||||||
|
{
|
||||||
|
leading: false,
|
||||||
|
trailing: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush logs to disk.
|
* Flush logs to disk.
|
||||||
*
|
*
|
||||||
|
@ -233,7 +251,7 @@ export class IndexedDBLogStore {
|
||||||
*
|
*
|
||||||
* @return {Promise} Resolved when the logs have been flushed.
|
* @return {Promise} Resolved when the logs have been flushed.
|
||||||
*/
|
*/
|
||||||
public flush(): Promise<void> {
|
public flush = (): Promise<void> => {
|
||||||
// check if a flush() operation is ongoing
|
// check if a flush() operation is ongoing
|
||||||
if (this.flushPromise) {
|
if (this.flushPromise) {
|
||||||
if (this.flushAgainPromise) {
|
if (this.flushAgainPromise) {
|
||||||
|
@ -258,20 +276,19 @@ export class IndexedDBLogStore {
|
||||||
reject(new Error("No connected database"));
|
reject(new Error("No connected database"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lines = this.logger.flush();
|
const lines = this.loggerInstance.popLogs();
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
|
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
|
||||||
const objStore = txn.objectStore("logs");
|
const objStore = txn.objectStore("logs");
|
||||||
txn.oncomplete = (event) => {
|
txn.oncomplete = () => {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = (event) => {
|
txn.onerror = (event) => {
|
||||||
logger.error("Failed to flush logs : ", event);
|
logger.error("Failed to flush logs : ", event);
|
||||||
// @ts-ignore
|
reject(new Error("Failed to write logs: " + txn.error.message));
|
||||||
reject(new Error("Failed to write logs: " + event.target.errorCode));
|
|
||||||
};
|
};
|
||||||
objStore.add(this.generateLogEntry(lines));
|
objStore.add(this.generateLogEntry(lines));
|
||||||
const lastModStore = txn.objectStore("logslastmod");
|
const lastModStore = txn.objectStore("logslastmod");
|
||||||
|
@ -280,7 +297,7 @@ export class IndexedDBLogStore {
|
||||||
this.flushPromise = null;
|
this.flushPromise = null;
|
||||||
});
|
});
|
||||||
return this.flushPromise;
|
return this.flushPromise;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consume the most recent logs and return them. Older logs which are not
|
* Consume the most recent logs and return them. Older logs which are not
|
||||||
|
@ -307,13 +324,11 @@ export class IndexedDBLogStore {
|
||||||
.index("id")
|
.index("id")
|
||||||
.openCursor(IDBKeyRange.only(id), "prev");
|
.openCursor(IDBKeyRange.only(id), "prev");
|
||||||
let lines = "";
|
let lines = "";
|
||||||
query.onerror = (event) => {
|
query.onerror = () => {
|
||||||
// @ts-ignore
|
reject(new Error("Query failed: " + query.error.message));
|
||||||
reject(new Error("Query failed: " + event.target.errorCode));
|
|
||||||
};
|
};
|
||||||
query.onsuccess = (event) => {
|
query.onsuccess = () => {
|
||||||
// @ts-ignore
|
const cursor = query.result;
|
||||||
const cursor = event.target.result;
|
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
resolve(lines);
|
resolve(lines);
|
||||||
return; // end of results
|
return; // end of results
|
||||||
|
@ -355,9 +370,8 @@ export class IndexedDBLogStore {
|
||||||
const o = txn.objectStore("logs");
|
const o = txn.objectStore("logs");
|
||||||
// only load the key path, not the data which may be huge
|
// only load the key path, not the data which may be huge
|
||||||
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
||||||
query.onsuccess = (event) => {
|
query.onsuccess = () => {
|
||||||
// @ts-ignore
|
const cursor = query.result;
|
||||||
const cursor = event.target.result;
|
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -367,12 +381,10 @@ export class IndexedDBLogStore {
|
||||||
txn.oncomplete = () => {
|
txn.oncomplete = () => {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
txn.onerror = (event) => {
|
txn.onerror = () => {
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
"Failed to delete logs for " +
|
"Failed to delete logs for " + `'${id}' : ${txn.error.message}`
|
||||||
// @ts-ignore
|
|
||||||
`'${id}' : ${event.target.errorCode}`
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -456,14 +468,12 @@ function selectQuery<T>(
|
||||||
const query = store.openCursor(keyRange);
|
const query = store.openCursor(keyRange);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const results = [];
|
const results = [];
|
||||||
query.onerror = (event) => {
|
query.onerror = () => {
|
||||||
// @ts-ignore
|
reject(new Error("Query failed: " + query.error.message));
|
||||||
reject(new Error("Query failed: " + event.target.errorCode));
|
|
||||||
};
|
};
|
||||||
// collect results
|
// collect results
|
||||||
query.onsuccess = (event) => {
|
query.onsuccess = () => {
|
||||||
// @ts-ignore
|
const cursor = query.result;
|
||||||
const cursor = event.target.result;
|
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
resolve(results);
|
resolve(results);
|
||||||
return; // end of results
|
return; // end of results
|
||||||
|
@ -479,8 +489,6 @@ declare global {
|
||||||
// eslint-disable-next-line no-var, camelcase
|
// eslint-disable-next-line no-var, camelcase
|
||||||
var mx_rage_logger: ConsoleLogger;
|
var mx_rage_logger: ConsoleLogger;
|
||||||
// eslint-disable-next-line no-var, camelcase
|
// 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>;
|
var mx_rage_initStoragePromise: Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,19 +499,11 @@ declare global {
|
||||||
* be set up immediately for the logs.
|
* be set up immediately for the logs.
|
||||||
* @return {Promise} Resolves when set up.
|
* @return {Promise} Resolves when set up.
|
||||||
*/
|
*/
|
||||||
export function init(setUpPersistence = true): Promise<void> {
|
export function init(): Promise<void> {
|
||||||
if (global.mx_rage_initPromise) {
|
|
||||||
return global.mx_rage_initPromise;
|
|
||||||
}
|
|
||||||
global.mx_rage_logger = new ConsoleLogger();
|
global.mx_rage_logger = new ConsoleLogger();
|
||||||
global.mx_rage_logger.monkeyPatch(window.console);
|
global.mx_rage_logger.monkeyPatch(window.console);
|
||||||
|
|
||||||
if (setUpPersistence) {
|
return tryInitStorage();
|
||||||
return tryInitStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
global.mx_rage_initPromise = Promise.resolve();
|
|
||||||
return global.mx_rage_initPromise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -573,7 +573,7 @@ export async function getLogsForReport(): Promise<LogEntry[]> {
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
lines: global.mx_rage_logger.flush(true),
|
lines: global.mx_rage_logger.peekLogs(),
|
||||||
id: "-",
|
id: "-",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -10362,9 +10362,9 @@ matrix-events-sdk@0.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#278dd6a3d35cf89c03f9172f9c81579577a267b3":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#ac97928a5d081b0502952295eea5b8fa7bdf8839":
|
||||||
version "23.0.0"
|
version "23.1.1"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/278dd6a3d35cf89c03f9172f9c81579577a267b3"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ac97928a5d081b0502952295eea5b8fa7bdf8839"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.2"
|
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.2"
|
||||||
|
|
Loading…
Add table
Reference in a new issue