Merge branch 'develop' into bump-matrix-wysiwyg-to-0.8.0
This commit is contained in:
commit
ec4e9c89ae
84 changed files with 529 additions and 193 deletions
|
@ -21,6 +21,10 @@ import { SynapseInstance } from "../../plugins/synapsedocker";
|
||||||
describe("Login", () => {
|
describe("Login", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.stubDefaultServer();
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
cy.stopSynapse(synapse);
|
cy.stopSynapse(synapse);
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ describe("Registration", () => {
|
||||||
let synapse: SynapseInstance;
|
let synapse: SynapseInstance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.stubDefaultServer();
|
||||||
cy.visit("/#/register");
|
cy.visit("/#/register");
|
||||||
cy.startSynapse("consent").then(data => {
|
cy.startSynapse("consent").then(data => {
|
||||||
synapse = data;
|
synapse = data;
|
||||||
|
|
48
cypress/fixtures/matrix-org-client-login.json
Normal file
48
cypress/fixtures/matrix-org-client-login.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"flows": [
|
||||||
|
{
|
||||||
|
"type": "m.login.sso",
|
||||||
|
"identity_providers": [
|
||||||
|
{
|
||||||
|
"id": "oidc-github",
|
||||||
|
"name": "GitHub",
|
||||||
|
"icon": "mxc://matrix.org/sVesTtrFDTpXRbYfpahuJsKP",
|
||||||
|
"brand": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "oidc-google",
|
||||||
|
"name": "Google",
|
||||||
|
"icon": "mxc://matrix.org/ZlnaaZNPxtUuQemvgQzlOlkz",
|
||||||
|
"brand": "google"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "oidc-gitlab",
|
||||||
|
"name": "GitLab",
|
||||||
|
"icon": "mxc://matrix.org/MCVOEmFgVieKFshPxmnejWOq",
|
||||||
|
"brand": "gitlab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "oidc-facebook",
|
||||||
|
"name": "Facebook",
|
||||||
|
"icon": "mxc://matrix.org/nsyeLIgzxazZmJadflMAsAWG",
|
||||||
|
"brand": "facebook"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "oidc-apple",
|
||||||
|
"name": "Apple",
|
||||||
|
"icon": "mxc://matrix.org/QQKNSOdLiMHtJhzeAObmkFiU",
|
||||||
|
"brand": "apple"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.login.token"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.login.password"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "m.login.application_service"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
39
cypress/fixtures/matrix-org-client-versions.json
Normal file
39
cypress/fixtures/matrix-org-client-versions.json
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"versions": [
|
||||||
|
"r0.0.1",
|
||||||
|
"r0.1.0",
|
||||||
|
"r0.2.0",
|
||||||
|
"r0.3.0",
|
||||||
|
"r0.4.0",
|
||||||
|
"r0.5.0",
|
||||||
|
"r0.6.0",
|
||||||
|
"r0.6.1",
|
||||||
|
"v1.1",
|
||||||
|
"v1.2",
|
||||||
|
"v1.3",
|
||||||
|
"v1.4"
|
||||||
|
],
|
||||||
|
"unstable_features": {
|
||||||
|
"org.matrix.label_based_filtering": true,
|
||||||
|
"org.matrix.e2e_cross_signing": true,
|
||||||
|
"org.matrix.msc2432": true,
|
||||||
|
"uk.half-shot.msc2666.mutual_rooms": true,
|
||||||
|
"io.element.e2ee_forced.public": false,
|
||||||
|
"io.element.e2ee_forced.private": false,
|
||||||
|
"io.element.e2ee_forced.trusted_private": false,
|
||||||
|
"org.matrix.msc3026.busy_presence": false,
|
||||||
|
"org.matrix.msc2285.stable": true,
|
||||||
|
"org.matrix.msc3827.stable": true,
|
||||||
|
"org.matrix.msc2716": false,
|
||||||
|
"org.matrix.msc3030": false,
|
||||||
|
"org.matrix.msc3440.stable": true,
|
||||||
|
"org.matrix.msc3771": true,
|
||||||
|
"org.matrix.msc3773": false,
|
||||||
|
"fi.mau.msc2815": false,
|
||||||
|
"org.matrix.msc3882": false,
|
||||||
|
"org.matrix.msc3881": false,
|
||||||
|
"org.matrix.msc3874": false,
|
||||||
|
"org.matrix.msc3886": false,
|
||||||
|
"org.matrix.msc3912": false
|
||||||
|
}
|
||||||
|
}
|
8
cypress/fixtures/matrix-org-client-well-known.json
Normal file
8
cypress/fixtures/matrix-org-client-well-known.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://matrix-client.matrix.org"
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
"base_url": "https://vector.im"
|
||||||
|
}
|
||||||
|
}
|
1
cypress/fixtures/vector-im-identity-v1.json
Normal file
1
cypress/fixtures/vector-im-identity-v1.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -20,10 +20,12 @@ declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
// Intercept all /_matrix/ networking requests for the logged in user and fail them
|
// Intercept all /_matrix/ networking requests for the logged-in user and fail them
|
||||||
goOffline(): void;
|
goOffline(): void;
|
||||||
// Remove intercept on all /_matrix/ networking requests
|
// Remove intercept on all /_matrix/ networking requests
|
||||||
goOnline(): void;
|
goOnline(): void;
|
||||||
|
// Intercept calls to vector.im/matrix.org so a login page can be shown offline
|
||||||
|
stubDefaultServer(): void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,5 +60,29 @@ Cypress.Commands.add("goOnline", (): void => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("stubDefaultServer", (): void => {
|
||||||
|
cy.log("Stubbing vector.im and matrix.org network calls");
|
||||||
|
// We intercept vector.im & matrix.org calls so that tests don't fail when it has issues
|
||||||
|
cy.intercept("GET", "https://vector.im/_matrix/identity/api/v1", {
|
||||||
|
fixture: "vector-im-identity-v1.json",
|
||||||
|
});
|
||||||
|
cy.intercept("GET", "https://matrix.org/.well-known/matrix/client", {
|
||||||
|
fixture: "matrix-org-client-well-known.json",
|
||||||
|
});
|
||||||
|
cy.intercept("GET", "https://matrix-client.matrix.org/_matrix/client/versions", {
|
||||||
|
fixture: "matrix-org-client-versions.json",
|
||||||
|
});
|
||||||
|
cy.intercept("GET", "https://matrix-client.matrix.org/_matrix/client/r0/login", {
|
||||||
|
fixture: "matrix-org-client-login.json",
|
||||||
|
});
|
||||||
|
cy.intercept("POST", "https://matrix-client.matrix.org/_matrix/client/r0/register?kind=guest", {
|
||||||
|
statusCode: 403,
|
||||||
|
body: {
|
||||||
|
errcode: "M_FORBIDDEN",
|
||||||
|
error: "Registration is not enabled on this homeserver.",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Needed to make this file a module
|
// Needed to make this file a module
|
||||||
export { };
|
export { };
|
||||||
|
|
|
@ -160,7 +160,7 @@
|
||||||
"@types/katex": "^0.14.0",
|
"@types/katex": "^0.14.0",
|
||||||
"@types/lodash": "^4.14.168",
|
"@types/lodash": "^4.14.168",
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^14.18.28",
|
"@types/node": "^16",
|
||||||
"@types/pako": "^1.0.1",
|
"@types/pako": "^1.0.1",
|
||||||
"@types/parse5": "^6.0.0",
|
"@types/parse5": "^6.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
|
@ -212,7 +212,7 @@
|
||||||
"stylelint": "^14.9.1",
|
"stylelint": "^14.9.1",
|
||||||
"stylelint-config-standard": "^26.0.0",
|
"stylelint-config-standard": "^26.0.0",
|
||||||
"stylelint-scss": "^4.2.0",
|
"stylelint-scss": "^4.2.0",
|
||||||
"typescript": "4.8.4",
|
"typescript": "4.9.3",
|
||||||
"walk": "^2.3.14"
|
"walk": "^2.3.14"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
|
|
@ -40,8 +40,9 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: $spacing-4;
|
gap: $spacing-4;
|
||||||
|
|
||||||
i {
|
.mx_Spinner {
|
||||||
flex-shrink: 0;
|
flex: 0 0 14px;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
4
src/@types/global.d.ts
vendored
4
src/@types/global.d.ts
vendored
|
@ -149,14 +149,10 @@ declare global {
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
|
// https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
|
||||||
interface OffscreenCanvas {
|
interface OffscreenCanvas {
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
getContext: HTMLCanvasElement["getContext"];
|
|
||||||
convertToBlob(opts?: {
|
convertToBlob(opts?: {
|
||||||
type?: string;
|
type?: string;
|
||||||
quality?: number;
|
quality?: number;
|
||||||
}): Promise<Blob>;
|
}): Promise<Blob>;
|
||||||
transferToImageBitmap(): ImageBitmap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLAudioElement {
|
interface HTMLAudioElement {
|
||||||
|
|
|
@ -174,12 +174,12 @@ export class DecryptionFailureTracker {
|
||||||
* Start checking for and tracking failures.
|
* Start checking for and tracking failures.
|
||||||
*/
|
*/
|
||||||
public start(): void {
|
public start(): void {
|
||||||
this.checkInterval = setInterval(
|
this.checkInterval = window.setInterval(
|
||||||
() => this.checkFailures(Date.now()),
|
() => this.checkFailures(Date.now()),
|
||||||
DecryptionFailureTracker.CHECK_INTERVAL_MS,
|
DecryptionFailureTracker.CHECK_INTERVAL_MS,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.trackInterval = setInterval(
|
this.trackInterval = window.setInterval(
|
||||||
() => this.trackFailures(),
|
() => this.trackFailures(),
|
||||||
DecryptionFailureTracker.TRACK_INTERVAL_MS,
|
DecryptionFailureTracker.TRACK_INTERVAL_MS,
|
||||||
);
|
);
|
||||||
|
|
|
@ -254,7 +254,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
|
logger.log("Failed to check for protocol support and no retries remain: assuming no support", e);
|
||||||
} else {
|
} else {
|
||||||
logger.log("Failed to check for protocol support: will retry", e);
|
logger.log("Failed to check for protocol support: will retry", e);
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.checkProtocols(maxTries - 1);
|
this.checkProtocols(maxTries - 1);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -584,7 +584,7 @@ async function doSetLoggedIn(
|
||||||
// later than MatrixChat might assume.
|
// later than MatrixChat might assume.
|
||||||
//
|
//
|
||||||
// we fire it *synchronously* to make sure it fires before on_logged_in.
|
// we fire it *synchronously* to make sure it fires before on_logged_in.
|
||||||
// (dis.dispatch uses `setTimeout`, which does not guarantee ordering.)
|
// (dis.dispatch uses `window.setTimeout`, which does not guarantee ordering.)
|
||||||
dis.dispatch({ action: 'on_logging_in' }, true);
|
dis.dispatch({ action: 'on_logging_in' }, true);
|
||||||
|
|
||||||
if (clearStorageEnabled) {
|
if (clearStorageEnabled) {
|
||||||
|
@ -865,7 +865,7 @@ export async function onLoggedOut(): Promise<void> {
|
||||||
if (SdkConfig.get().logout_redirect_url) {
|
if (SdkConfig.get().logout_redirect_url) {
|
||||||
logger.log("Redirecting to external provider to finish logout");
|
logger.log("Redirecting to external provider to finish logout");
|
||||||
// XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login
|
// XXX: Defer this so that it doesn't race with MatrixChat unmounting the world by going to /#/login
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
window.location.href = SdkConfig.get().logout_redirect_url;
|
window.location.href = SdkConfig.get().logout_redirect_url;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// and then we animate to the resting state
|
// and then we animate to the resting state
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.applyStyles(domNode as HTMLElement, restingStyle);
|
this.applyStyles(domNode as HTMLElement, restingStyle);
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ export default class PasswordReset {
|
||||||
this.checkEmailLinkClicked()
|
this.checkEmailLinkClicked()
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setTimeout(
|
window.setTimeout(
|
||||||
() => this.tryCheckEmailLinkClicked(resolve),
|
() => this.tryCheckEmailLinkClicked(resolve),
|
||||||
CHECK_EMAIL_VERIFIED_POLL_INTERVAL,
|
CHECK_EMAIL_VERIFIED_POLL_INTERVAL,
|
||||||
);
|
);
|
||||||
|
|
|
@ -127,7 +127,7 @@ export class PlaybackClock implements IDestroyable {
|
||||||
// cast to number because the types are wrong
|
// cast to number because the types are wrong
|
||||||
// 100ms interval to make sure the time is as accurate as possible without
|
// 100ms interval to make sure the time is as accurate as possible without
|
||||||
// being overly insane
|
// being overly insane
|
||||||
this.timerId = <number><any>setInterval(this.checkTime, 100);
|
this.timerId = <number><any>window.setInterval(this.checkTime, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ export interface ISelectionRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICompletion {
|
export interface ICompletion {
|
||||||
type: "at-room" | "command" | "community" | "room" | "user";
|
type?: "at-room" | "command" | "community" | "room" | "user";
|
||||||
completion: string;
|
completion: string;
|
||||||
completionId?: string;
|
completionId?: string;
|
||||||
component?: ReactElement;
|
component?: ReactElement;
|
||||||
|
|
|
@ -103,7 +103,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
return []; // don't give any suggestions if the user doesn't want them
|
return []; // don't give any suggestions if the user doesn't want them
|
||||||
}
|
}
|
||||||
|
|
||||||
let completions = [];
|
let completions: ISortedEmoji[] = [];
|
||||||
const { command, range } = this.getCurrentCommand(query, selection);
|
const { command, range } = this.getCurrentCommand(query, selection);
|
||||||
|
|
||||||
if (command && command[0].length > 2) {
|
if (command && command[0].length > 2) {
|
||||||
|
@ -132,7 +132,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
// Finally, sort by original ordering
|
// Finally, sort by original ordering
|
||||||
sorters.push(c => c._orderBy);
|
sorters.push(c => c._orderBy);
|
||||||
completions = sortBy(uniq(completions), sorters);
|
completions = sortBy<ISortedEmoji>(uniq(completions), sorters);
|
||||||
|
|
||||||
completions = completions.slice(0, LIMIT);
|
completions = completions.slice(0, LIMIT);
|
||||||
|
|
||||||
|
@ -141,9 +141,9 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
this.recentlyUsed.forEach(emoji => {
|
this.recentlyUsed.forEach(emoji => {
|
||||||
sorters.push(c => score(emoji.shortcodes[0], c.emoji.shortcodes[0]));
|
sorters.push(c => score(emoji.shortcodes[0], c.emoji.shortcodes[0]));
|
||||||
});
|
});
|
||||||
completions = sortBy(uniq(completions), sorters);
|
completions = sortBy<ISortedEmoji>(uniq(completions), sorters);
|
||||||
|
|
||||||
completions = completions.map(c => ({
|
return completions.map(c => ({
|
||||||
completion: c.emoji.unicode,
|
completion: c.emoji.unicode,
|
||||||
component: (
|
component: (
|
||||||
<PillCompletion title={`:${c.emoji.shortcodes[0]}:`} aria-label={c.emoji.unicode}>
|
<PillCompletion title={`:${c.emoji.shortcodes[0]}:`} aria-label={c.emoji.unicode}>
|
||||||
|
@ -153,7 +153,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
range,
|
range,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return completions;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.poll) {
|
if (this.props.poll) {
|
||||||
this.intervalId = setInterval(() => {
|
this.intervalId = window.setInterval(() => {
|
||||||
this.authLogic.poll();
|
this.authLogic.poll();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1965,7 +1965,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.accountPassword = password;
|
this.accountPassword = password;
|
||||||
// self-destruct the password after 5mins
|
// self-destruct the password after 5mins
|
||||||
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
|
||||||
this.accountPasswordTimer = setTimeout(() => {
|
this.accountPasswordTimer = window.setTimeout(() => {
|
||||||
this.accountPassword = null;
|
this.accountPassword = null;
|
||||||
this.accountPasswordTimer = null;
|
this.accountPasswordTimer = null;
|
||||||
}, 60 * 5 * 1000);
|
}, 60 * 5 * 1000);
|
||||||
|
|
|
@ -459,7 +459,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
if (this.unfillDebouncer) {
|
if (this.unfillDebouncer) {
|
||||||
clearTimeout(this.unfillDebouncer);
|
clearTimeout(this.unfillDebouncer);
|
||||||
}
|
}
|
||||||
this.unfillDebouncer = setTimeout(() => {
|
this.unfillDebouncer = window.setTimeout(() => {
|
||||||
this.unfillDebouncer = null;
|
this.unfillDebouncer = null;
|
||||||
debuglog("unfilling now", { backwards, origExcessHeight });
|
debuglog("unfilling now", { backwards, origExcessHeight });
|
||||||
this.props.onUnfillRequest?.(backwards, markerScrollToken!);
|
this.props.onUnfillRequest?.(backwards, markerScrollToken!);
|
||||||
|
@ -485,7 +485,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
// this will block the scroll event handler for +700ms
|
// this will block the scroll event handler for +700ms
|
||||||
// if messages are already cached in memory,
|
// if messages are already cached in memory,
|
||||||
// This would cause jumping to happen on Chrome/macOS.
|
// This would cause jumping to happen on Chrome/macOS.
|
||||||
return new Promise(resolve => setTimeout(resolve, 1)).then(() => {
|
return new Promise(resolve => window.setTimeout(resolve, 1)).then(() => {
|
||||||
return this.props.onFillRequest(backwards);
|
return this.props.onFillRequest(backwards);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.pendingFillRequests[dir] = false;
|
this.pendingFillRequests[dir] = false;
|
||||||
|
|
|
@ -697,7 +697,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
if (this.debounceTimer) {
|
if (this.debounceTimer) {
|
||||||
clearTimeout(this.debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
}
|
}
|
||||||
this.debounceTimer = setTimeout(() => {
|
this.debounceTimer = window.setTimeout(() => {
|
||||||
this.updateSuggestions(term);
|
this.updateSuggestions(term);
|
||||||
}, 150); // 150ms debounce (human reaction time + some)
|
}, 150); // 150ms debounce (human reaction time + some)
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,7 +48,7 @@ async function syncHealthCheck(cli: MatrixClient): Promise<void> {
|
||||||
*/
|
*/
|
||||||
async function proxyHealthCheck(endpoint: string, hsUrl?: string): Promise<void> {
|
async function proxyHealthCheck(endpoint: string, hsUrl?: string): Promise<void> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const id = setTimeout(() => controller.abort(), 10 * 1000); // 10s
|
const id = window.setTimeout(() => controller.abort(), 10 * 1000); // 10s
|
||||||
const res = await fetch(endpoint + "/client/server.json", {
|
const res = await fetch(endpoint + "/client/server.json", {
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,7 +51,7 @@ const VerificationRequestExplorer: React.FC<{
|
||||||
if (request.timeout == 0) return;
|
if (request.timeout == 0) return;
|
||||||
|
|
||||||
/* Note that request.timeout is a getter, so its value changes */
|
/* Note that request.timeout is a getter, so its value changes */
|
||||||
const id = setInterval(() => {
|
const id = window.setInterval(() => {
|
||||||
setRequestTimeout(request.timeout);
|
setRequestTimeout(request.timeout);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
|
|
@ -228,7 +228,7 @@ export const useWebSearchMetrics = (numResults: number, queryLength: number, via
|
||||||
if (!queryLength) return;
|
if (!queryLength) return;
|
||||||
|
|
||||||
// send metrics after a 1s debounce
|
// send metrics after a 1s debounce
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
PosthogAnalytics.instance.trackEvent<WebSearchEvent>({
|
PosthogAnalytics.instance.trackEvent<WebSearchEvent>({
|
||||||
eventName: "WebSearch",
|
eventName: "WebSearch",
|
||||||
viaSpotlight,
|
viaSpotlight,
|
||||||
|
|
|
@ -106,7 +106,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
// setInterval() first waits and then executes, therefore
|
// window.setInterval() first waits and then executes, therefore
|
||||||
// we call getDesktopCapturerSources() here without any delay.
|
// we call getDesktopCapturerSources() here without any delay.
|
||||||
// Otherwise the dialog would be left empty for some time.
|
// Otherwise the dialog would be left empty for some time.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -114,7 +114,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<
|
||||||
});
|
});
|
||||||
|
|
||||||
// We update the sources every 500ms to get newer thumbnails
|
// We update the sources every 500ms to get newer thumbnails
|
||||||
this.interval = setInterval(async () => {
|
this.interval = window.setInterval(async () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
sources: await getDesktopCapturerSources(),
|
sources: await getDesktopCapturerSources(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function UseCaseSelection({ onFinished }: Props) {
|
||||||
// Call onFinished 1.5s after `selection` becomes truthy, to give time for the animation to run
|
// Call onFinished 1.5s after `selection` becomes truthy, to give time for the animation to run
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selection) {
|
if (selection) {
|
||||||
let handler: number | null = setTimeout(() => {
|
let handler: number | null = window.setTimeout(() => {
|
||||||
handler = null;
|
handler = null;
|
||||||
onFinished(selection);
|
onFinished(selection);
|
||||||
}, TIMEOUT);
|
}, TIMEOUT);
|
||||||
|
|
|
@ -191,7 +191,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
|
||||||
this.setState({ filter });
|
this.setState({ filter });
|
||||||
// Header underlines need to be updated, but updating requires knowing
|
// Header underlines need to be updated, but updating requires knowing
|
||||||
// where the categories are, so we wait for a tick.
|
// where the categories are, so we wait for a tick.
|
||||||
setTimeout(this.updateVisibility, 0);
|
window.setTimeout(this.updateVisibility, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => {
|
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => {
|
||||||
|
|
|
@ -31,8 +31,8 @@ class Search extends React.PureComponent<IProps> {
|
||||||
private inputRef = React.createRef<HTMLInputElement>();
|
private inputRef = React.createRef<HTMLInputElement>();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// For some reason, neither the autoFocus nor just calling focus() here worked, so here's a setTimeout
|
// For some reason, neither the autoFocus nor just calling focus() here worked, so here's a window.setTimeout
|
||||||
setTimeout(() => this.inputRef.current.focus(), 0);
|
window.setTimeout(() => this.inputRef.current.focus(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
|
|
|
@ -335,7 +335,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
// Add a 150ms timer for blurhash to first appear.
|
// Add a 150ms timer for blurhash to first appear.
|
||||||
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) {
|
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) {
|
||||||
this.clearBlurhashTimeout();
|
this.clearBlurhashTimeout();
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = window.setTimeout(() => {
|
||||||
if (!this.state.imgLoaded || !this.state.imgError) {
|
if (!this.state.imgLoaded || !this.state.imgError) {
|
||||||
this.setState({
|
this.setState({
|
||||||
placeholder: Placeholder.Blurhash,
|
placeholder: Placeholder.Blurhash,
|
||||||
|
|
|
@ -130,7 +130,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
||||||
if (codes.length > 0) {
|
if (codes.length > 0) {
|
||||||
// Do this asynchronously: parsing code takes time and we don't
|
// Do this asynchronously: parsing code takes time and we don't
|
||||||
// need to block the DOM update on it.
|
// need to block the DOM update on it.
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
if (this.unmounted) return;
|
if (this.unmounted) return;
|
||||||
for (let i = 0; i < codes.length; i++) {
|
for (let i = 0; i < codes.length; i++) {
|
||||||
this.highlightCode(codes[i]);
|
this.highlightCode(codes[i]);
|
||||||
|
|
|
@ -111,8 +111,21 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
|
||||||
const onStartVerification = useCallback(async () => {
|
const onStartVerification = useCallback(async () => {
|
||||||
setRequesting(true);
|
setRequesting(true);
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
let verificationRequest_: VerificationRequest;
|
||||||
|
try {
|
||||||
const roomId = await ensureDMExists(cli, member.userId);
|
const roomId = await ensureDMExists(cli, member.userId);
|
||||||
const verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
|
verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error starting verification", e);
|
||||||
|
setRequesting(false);
|
||||||
|
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
headerImage: require("../../../../res/img/e2e/warning.svg").default,
|
||||||
|
title: _t("Error starting verification"),
|
||||||
|
description: _t("We were unable to start a chat with the other user."),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
setRequest(verificationRequest_);
|
setRequest(verificationRequest_);
|
||||||
setPhase(verificationRequest_.phase);
|
setPhase(verificationRequest_.phase);
|
||||||
// Notify the RightPanelStore about this
|
// Notify the RightPanelStore about this
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.debounceCompletionsRequest = setTimeout(() => {
|
this.debounceCompletionsRequest = window.setTimeout(() => {
|
||||||
resolve(this.processQuery(query, selection));
|
resolve(this.processQuery(query, selection));
|
||||||
}, autocompleteDelay);
|
}, autocompleteDelay);
|
||||||
});
|
});
|
||||||
|
|
|
@ -199,7 +199,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
// that the ScrollPanel listening to the resizeNotifier can
|
// that the ScrollPanel listening to the resizeNotifier can
|
||||||
// correctly measure it's new height and scroll down to keep
|
// correctly measure it's new height and scroll down to keep
|
||||||
// at the bottom if it already is
|
// at the bottom if it already is
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.props.resizeNotifier.notifyTimelineHeightChanged();
|
this.props.resizeNotifier.notifyTimelineHeightChanged();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
@ -395,7 +395,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onRecordingEndingSoon = ({ secondsLeft }) => {
|
private onRecordingEndingSoon = ({ secondsLeft }) => {
|
||||||
this.setState({ recordingTimeLeftSeconds: secondsLeft });
|
this.setState({ recordingTimeLeftSeconds: secondsLeft });
|
||||||
setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000);
|
window.setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
private setStickerPickerOpen = (isStickerPickerOpen: boolean) => {
|
private setStickerPickerOpen = (isStickerPickerOpen: boolean) => {
|
||||||
|
@ -584,6 +584,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
setUpVoiceBroadcastPreRecording(
|
setUpVoiceBroadcastPreRecording(
|
||||||
this.props.room,
|
this.props.room,
|
||||||
MatrixClientPeg.get(),
|
MatrixClientPeg.get(),
|
||||||
|
SdkContextClass.instance.voiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastRecordingsStore.instance(),
|
VoiceBroadcastRecordingsStore.instance(),
|
||||||
SdkContextClass.instance.voiceBroadcastPreRecordingStore,
|
SdkContextClass.instance.voiceBroadcastPreRecordingStore,
|
||||||
);
|
);
|
||||||
|
|
|
@ -99,7 +99,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
||||||
// again and this time we want to show the newest breadcrumb because it'll be hidden
|
// again and this time we want to show the newest breadcrumb because it'll be hidden
|
||||||
// off screen for the animation.
|
// off screen for the animation.
|
||||||
this.setState({ doAnimation: false, skipFirst: true });
|
this.setState({ doAnimation: false, skipFirst: true });
|
||||||
setTimeout(() => this.setState({ doAnimation: true, skipFirst: false }), 0);
|
window.setTimeout(() => this.setState({ doAnimation: true, skipFirst: false }), 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
private viewRoom = (room: Room, index: number, viaKeyboard = false) => {
|
private viewRoom = (room: Room, index: number, viaKeyboard = false) => {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function useIsFocused() {
|
||||||
} else {
|
} else {
|
||||||
// To avoid a blink when we switch mode between plain text and rich text mode
|
// To avoid a blink when we switch mode between plain text and rich text mode
|
||||||
// We delay the unfocused action
|
// We delay the unfocused action
|
||||||
timeoutIDRef.current = setTimeout(() => setIsFocused(false), 100);
|
timeoutIDRef.current = window.setTimeout(() => setIsFocused(false), 100);
|
||||||
}
|
}
|
||||||
}, [setIsFocused, timeoutIDRef]);
|
}, [setIsFocused, timeoutIDRef]);
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function focusComposer(
|
||||||
if (timeoutId.current) {
|
if (timeoutId.current) {
|
||||||
clearTimeout(timeoutId.current);
|
clearTimeout(timeoutId.current);
|
||||||
}
|
}
|
||||||
timeoutId.current = setTimeout(
|
timeoutId.current = window.setTimeout(
|
||||||
() => composerElement.current?.focus(),
|
() => composerElement.current?.focus(),
|
||||||
200,
|
200,
|
||||||
);
|
);
|
||||||
|
|
|
@ -150,7 +150,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
|
||||||
await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
|
await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
|
||||||
this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } });
|
this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } });
|
||||||
|
|
||||||
this.themeTimer = setTimeout(() => {
|
this.themeTimer = window.setTimeout(() => {
|
||||||
this.setState({ customThemeMessage: { text: "", isError: false } });
|
this.setState({ customThemeMessage: { text: "", isError: false } });
|
||||||
}, 3000);
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
|
@ -127,7 +127,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||||
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
|
||||||
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
const scrollIntoViewTimeoutRef = useRef<number>();
|
||||||
|
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const userId = matrixClient.getUserId();
|
const userId = matrixClient.getUserId();
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { request } = this.props;
|
const { request } = this.props;
|
||||||
if (request.timeout && request.timeout > 0) {
|
if (request.timeout && request.timeout > 0) {
|
||||||
this.intervalHandle = setInterval(() => {
|
this.intervalHandle = window.setInterval(() => {
|
||||||
let { counter } = this.state;
|
let { counter } = this.state;
|
||||||
counter = Math.max(0, counter - 1);
|
counter = Math.max(0, counter - 1);
|
||||||
this.setState({ counter });
|
this.setState({ counter });
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function UserOnboardingPage({ justRegistered = false }: Props) {
|
||||||
const [showList, setShowList] = useState<boolean>(false);
|
const [showList, setShowList] = useState<boolean>(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialSyncComplete) {
|
if (initialSyncComplete) {
|
||||||
let handler: number | null = setTimeout(() => {
|
let handler: number | null = window.setTimeout(() => {
|
||||||
handler = null;
|
handler = null;
|
||||||
setShowList(true);
|
setShowList(true);
|
||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
|
|
|
@ -43,7 +43,7 @@ interface GroupCallDurationProps {
|
||||||
export const GroupCallDuration: FC<GroupCallDurationProps> = ({ groupCall }) => {
|
export const GroupCallDuration: FC<GroupCallDurationProps> = ({ groupCall }) => {
|
||||||
const [now, setNow] = useState(() => Date.now());
|
const [now, setNow] = useState(() => Date.now());
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(() => setNow(Date.now()), 1000);
|
const timer = window.setInterval(() => setNow(Date.now()), 1000);
|
||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
@ -367,14 +367,14 @@ class PipView extends React.Component<IProps, IState> {
|
||||||
const pipMode = true;
|
const pipMode = true;
|
||||||
let pipContent: CreatePipChildren | null = null;
|
let pipContent: CreatePipChildren | null = null;
|
||||||
|
|
||||||
if (this.props.voiceBroadcastPreRecording) {
|
|
||||||
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.voiceBroadcastPlayback) {
|
if (this.props.voiceBroadcastPlayback) {
|
||||||
pipContent = this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback);
|
pipContent = this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.voiceBroadcastPreRecording) {
|
||||||
|
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.voiceBroadcastRecording) {
|
if (this.props.voiceBroadcastRecording) {
|
||||||
pipContent = this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording);
|
pipContent = this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class MatrixDispatcher extends Dispatcher<ActionPayload> {
|
||||||
// if you dispatch from within a dispatch, so rather than action
|
// if you dispatch from within a dispatch, so rather than action
|
||||||
// handlers having to worry about not calling anything that might
|
// handlers having to worry about not calling anything that might
|
||||||
// then dispatch, we just do dispatches asynchronously.
|
// then dispatch, we just do dispatches asynchronously.
|
||||||
setTimeout(super.dispatch.bind(this, payload), 0);
|
window.setTimeout(super.dispatch.bind(this, payload), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function useDebouncedCallback<T extends any[]>(
|
||||||
callback(...params);
|
callback(...params);
|
||||||
};
|
};
|
||||||
if (enabled !== false) {
|
if (enabled !== false) {
|
||||||
handle = setTimeout(doSearch, DEBOUNCE_TIMEOUT);
|
handle = window.setTimeout(doSearch, DEBOUNCE_TIMEOUT);
|
||||||
return () => {
|
return () => {
|
||||||
if (handle) {
|
if (handle) {
|
||||||
clearTimeout(handle);
|
clearTimeout(handle);
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const useTimeout = (handler: Handler, timeoutMs: number) => {
|
||||||
|
|
||||||
// Set up timer
|
// Set up timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeoutID = setTimeout(() => {
|
const timeoutID = window.setTimeout(() => {
|
||||||
savedHandler.current();
|
savedHandler.current();
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
return () => clearTimeout(timeoutID);
|
return () => clearTimeout(timeoutID);
|
||||||
|
@ -49,7 +49,7 @@ export const useInterval = (handler: Handler, intervalMs: number) => {
|
||||||
|
|
||||||
// Set up timer
|
// Set up timer
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const intervalID = setInterval(() => {
|
const intervalID = window.setInterval(() => {
|
||||||
savedHandler.current();
|
savedHandler.current();
|
||||||
}, intervalMs);
|
}, intervalMs);
|
||||||
return () => clearInterval(intervalID);
|
return () => clearInterval(intervalID);
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const useTimeoutToggle = (defaultValue: boolean, timeoutMs: number) => {
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = () => {
|
||||||
setValue(!defaultValue);
|
setValue(!defaultValue);
|
||||||
timeoutId.current = setTimeout(() => setValue(defaultValue), timeoutMs);
|
timeoutId.current = window.setTimeout(() => setValue(defaultValue), timeoutMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -68,7 +68,7 @@ function useUserOnboardingContextValue<T>(defaultValue: T, callback: (cli: Matri
|
||||||
}
|
}
|
||||||
setValue(await handler(cli));
|
setValue(await handler(cli));
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
handle = setTimeout(repeater, USER_ONBOARDING_CONTEXT_INTERVAL);
|
handle = window.setTimeout(repeater, USER_ONBOARDING_CONTEXT_INTERVAL);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
repeater().catch(err => logger.warn("could not update user onboarding context", err));
|
repeater().catch(err => logger.warn("could not update user onboarding context", err));
|
||||||
|
|
|
@ -660,6 +660,7 @@
|
||||||
"Change input device": "Change input device",
|
"Change input device": "Change input device",
|
||||||
"Live": "Live",
|
"Live": "Live",
|
||||||
"Voice broadcast": "Voice broadcast",
|
"Voice broadcast": "Voice broadcast",
|
||||||
|
"Buffering…": "Buffering…",
|
||||||
"Cannot reach homeserver": "Cannot reach homeserver",
|
"Cannot reach homeserver": "Cannot reach homeserver",
|
||||||
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
|
"Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin",
|
||||||
"Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured",
|
"Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured",
|
||||||
|
@ -2151,6 +2152,8 @@
|
||||||
"The homeserver the user you're verifying is connected to": "The homeserver the user you're verifying is connected to",
|
"The homeserver the user you're verifying is connected to": "The homeserver the user you're verifying is connected to",
|
||||||
"Yours, or the other users' internet connection": "Yours, or the other users' internet connection",
|
"Yours, or the other users' internet connection": "Yours, or the other users' internet connection",
|
||||||
"Yours, or the other users' session": "Yours, or the other users' session",
|
"Yours, or the other users' session": "Yours, or the other users' session",
|
||||||
|
"Error starting verification": "Error starting verification",
|
||||||
|
"We were unable to start a chat with the other user.": "We were unable to start a chat with the other user.",
|
||||||
"Nothing pinned, yet": "Nothing pinned, yet",
|
"Nothing pinned, yet": "Nothing pinned, yet",
|
||||||
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
"If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.": "If you have permissions, open the menu on any message and select <b>Pin</b> to stick them here.",
|
||||||
"Pinned messages": "Pinned messages",
|
"Pinned messages": "Pinned messages",
|
||||||
|
|
|
@ -377,7 +377,7 @@ export class JitsiCall extends Call {
|
||||||
|
|
||||||
this.participants = participants;
|
this.participants = participants;
|
||||||
if (allExpireAt < Infinity) {
|
if (allExpireAt < Infinity) {
|
||||||
this.participantsExpirationTimer = setTimeout(() => this.updateParticipants(), allExpireAt - now);
|
this.participantsExpirationTimer = window.setTimeout(() => this.updateParticipants(), allExpireAt - now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,7 +553,7 @@ export class JitsiCall extends Call {
|
||||||
// Tell others that we're connected, by adding our device to room state
|
// Tell others that we're connected, by adding our device to room state
|
||||||
await this.addOurDevice();
|
await this.addOurDevice();
|
||||||
// Re-add this device every so often so our video member event doesn't become stale
|
// Re-add this device every so often so our video member event doesn't become stale
|
||||||
this.resendDevicesTimer = setInterval(async () => {
|
this.resendDevicesTimer = window.setInterval(async () => {
|
||||||
logger.log(`Resending video member event for ${this.roomId}`);
|
logger.log(`Resending video member event for ${this.roomId}`);
|
||||||
await this.addOurDevice();
|
await this.addOurDevice();
|
||||||
}, (this.STUCK_DEVICE_TIMEOUT_MS * 3) / 4);
|
}, (this.STUCK_DEVICE_TIMEOUT_MS * 3) / 4);
|
||||||
|
@ -814,7 +814,7 @@ export class ElementCall extends Call {
|
||||||
// randomly between 2 and 8 seconds before terminating the call, to
|
// randomly between 2 and 8 seconds before terminating the call, to
|
||||||
// probabilistically reduce event spam. If someone else beats us to it,
|
// probabilistically reduce event spam. If someone else beats us to it,
|
||||||
// this timer will be automatically cleared upon the call's destruction.
|
// this timer will be automatically cleared upon the call's destruction.
|
||||||
this.terminationTimer = setTimeout(
|
this.terminationTimer = window.setTimeout(
|
||||||
() => this.groupCall.terminate(),
|
() => this.groupCall.terminate(),
|
||||||
Math.random() * 6000 + 2000,
|
Math.random() * 6000 + 2000,
|
||||||
);
|
);
|
||||||
|
|
|
@ -154,7 +154,7 @@ export class IndexedDBLogStore {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.db = event.target.result;
|
this.db = event.target.result;
|
||||||
// Periodically flush logs to local storage / indexeddb
|
// Periodically flush logs to local storage / indexeddb
|
||||||
setInterval(this.flush.bind(this), FLUSH_RATE_MS);
|
window.setInterval(this.flush.bind(this), FLUSH_RATE_MS);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -437,7 +437,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.locationInterval = setInterval(() => {
|
this.locationInterval = window.setInterval(() => {
|
||||||
if (!this.lastPublishedPositionTimestamp) {
|
if (!this.lastPublishedPositionTimestamp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> implements
|
||||||
if (!room) {
|
if (!room) {
|
||||||
logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`);
|
logger.warn(`Live timeline event ${eventPayload.event.getId()} received without associated room`);
|
||||||
logger.warn(`Queuing failed room update for retry as a result.`);
|
logger.warn(`Queuing failed room update for retry as a result.`);
|
||||||
setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
const updatedRoom = this.matrixClient.getRoom(roomId);
|
const updatedRoom = this.matrixClient.getRoom(roomId);
|
||||||
await tryUpdate(updatedRoom);
|
await tryUpdate(updatedRoom);
|
||||||
}, 100); // 100ms should be enough for the room to show up
|
}, 100); // 100ms should be enough for the room to show up
|
||||||
|
|
|
@ -298,7 +298,7 @@ export async function setTheme(theme?: string): Promise<void> {
|
||||||
|
|
||||||
// In case of theme toggling (white => black => white)
|
// In case of theme toggling (white => black => white)
|
||||||
// Chrome doesn't fire the `load` event when the white theme is selected the second times
|
// Chrome doesn't fire the `load` event when the white theme is selected the second times
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = window.setInterval(() => {
|
||||||
if (isStyleSheetLoaded()) {
|
if (isStyleSheetLoaded()) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
styleSheet.onload = undefined;
|
styleSheet.onload = undefined;
|
||||||
|
|
|
@ -241,7 +241,7 @@ export default class MultiInviter {
|
||||||
break;
|
break;
|
||||||
case "M_LIMIT_EXCEEDED":
|
case "M_LIMIT_EXCEEDED":
|
||||||
// we're being throttled so wait a bit & try again
|
// we're being throttled so wait a bit & try again
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.doInvite(address, ignoreProfile).then(resolve, reject);
|
this.doInvite(address, ignoreProfile).then(resolve, reject);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -55,7 +55,7 @@ export default class Timer {
|
||||||
this.setNotStarted();
|
this.setNotStarted();
|
||||||
} else {
|
} else {
|
||||||
const delta = this.timeout - elapsed;
|
const delta = this.timeout - elapsed;
|
||||||
this.timerHandle = setTimeout(this.onTimeout, delta);
|
this.timerHandle = window.setTimeout(this.onTimeout, delta);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ export default class Timer {
|
||||||
start() {
|
start() {
|
||||||
if (!this.isRunning()) {
|
if (!this.isRunning()) {
|
||||||
this.startTs = Date.now();
|
this.startTs = Date.now();
|
||||||
this.timerHandle = setTimeout(this.onTimeout, this.timeout);
|
this.timerHandle = window.setTimeout(this.onTimeout, this.timeout);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ export default class WidgetUtils {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const timerId = setTimeout(() => {
|
const timerId = window.setTimeout(() => {
|
||||||
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, onAccountData);
|
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, onAccountData);
|
||||||
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
||||||
}, WIDGET_WAIT_TIME);
|
}, WIDGET_WAIT_TIME);
|
||||||
|
@ -221,7 +221,7 @@ export default class WidgetUtils {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const timerId = setTimeout(() => {
|
const timerId = window.setTimeout(() => {
|
||||||
MatrixClientPeg.get().removeListener(RoomStateEvent.Events, onRoomStateEvents);
|
MatrixClientPeg.get().removeListener(RoomStateEvent.Events, onRoomStateEvents);
|
||||||
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
reject(new Error("Timed out waiting for widget ID " + widgetId + " to appear"));
|
||||||
}, WIDGET_WAIT_TIME);
|
}, WIDGET_WAIT_TIME);
|
||||||
|
|
|
@ -27,7 +27,7 @@ function showToast(text) {
|
||||||
const el = document.getElementById("snackbar");
|
const el = document.getElementById("snackbar");
|
||||||
el.innerHTML = text;
|
el.innerHTML = text;
|
||||||
el.className = "mx_show";
|
el.className = "mx_show";
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
el.className = el.className.replace("mx_show", "");
|
el.className = el.className.replace("mx_show", "");
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,10 +77,10 @@ export async function createThumbnail(
|
||||||
}
|
}
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement | OffscreenCanvas;
|
let canvas: HTMLCanvasElement | OffscreenCanvas;
|
||||||
let context: CanvasRenderingContext2D;
|
let context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
||||||
try {
|
try {
|
||||||
canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
|
canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
|
||||||
context = canvas.getContext("2d");
|
context = canvas.getContext("2d") as OffscreenCanvasRenderingContext2D;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fallback support for other browsers (Safari and Firefox for now)
|
// Fallback support for other browsers (Safari and Firefox for now)
|
||||||
canvas = document.createElement("canvas");
|
canvas = document.createElement("canvas");
|
||||||
|
@ -92,7 +92,7 @@ export async function createThumbnail(
|
||||||
context.drawImage(element, 0, 0, targetWidth, targetHeight);
|
context.drawImage(element, 0, 0, targetWidth, targetHeight);
|
||||||
|
|
||||||
let thumbnailPromise: Promise<Blob>;
|
let thumbnailPromise: Promise<Blob>;
|
||||||
if (window.OffscreenCanvas && canvas instanceof window.OffscreenCanvas) {
|
if (window.OffscreenCanvas && canvas instanceof OffscreenCanvas) {
|
||||||
thumbnailPromise = canvas.convertToBlob({ type: mimeType });
|
thumbnailPromise = canvas.convertToBlob({ type: mimeType });
|
||||||
} else {
|
} else {
|
||||||
thumbnailPromise = new Promise<Blob>(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
|
thumbnailPromise = new Promise<Blob>(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
|
||||||
|
|
|
@ -102,10 +102,10 @@ export async function waitForRoomReadyAndApplyAfterCreateCallbacks(
|
||||||
finish();
|
finish();
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkRoomStateIntervalHandle = setInterval(() => {
|
const checkRoomStateIntervalHandle = window.setInterval(() => {
|
||||||
if (isRoomReady(client, localRoom)) finish();
|
if (isRoomReady(client, localRoom)) finish();
|
||||||
}, 500);
|
}, 500);
|
||||||
const stopgapTimeoutHandle = setTimeout(stopgapFinish, 5000);
|
const stopgapTimeoutHandle = window.setTimeout(stopgapFinish, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ export async function waitForMember(client: MatrixClient, roomId: string, userId
|
||||||
|
|
||||||
/* We don't want to hang if this goes wrong, so we proceed and hope the other
|
/* We don't want to hang if this goes wrong, so we proceed and hope the other
|
||||||
user is already in the megolm session */
|
user is already in the megolm session */
|
||||||
setTimeout(resolve, timeout, false);
|
window.setTimeout(resolve, timeout, false);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
client.removeListener(RoomStateEvent.NewMember, handler);
|
client.removeListener(RoomStateEvent.NewMember, handler);
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
// or when the timeout of ms is reached with the value of given timeoutValue
|
// or when the timeout of ms is reached with the value of given timeoutValue
|
||||||
export async function timeout<T, Y>(promise: Promise<T>, timeoutValue: Y, ms: number): Promise<T | Y> {
|
export async function timeout<T, Y>(promise: Promise<T>, timeoutValue: Y, ms: number): Promise<T | Y> {
|
||||||
const timeoutPromise = new Promise<T | Y>((resolve) => {
|
const timeoutPromise = new Promise<T | Y>((resolve) => {
|
||||||
const timeoutId = setTimeout(resolve, ms, timeoutValue);
|
const timeoutId = window.setTimeout(resolve, ms, timeoutValue);
|
||||||
promise.then(() => {
|
promise.then(() => {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from "../../../components/views/elements/AccessibleButto
|
||||||
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
import { Icon as XIcon } from "../../../../res/img/element-icons/cancel-rounded.svg";
|
||||||
import Clock from "../../../components/views/audio_messages/Clock";
|
import Clock from "../../../components/views/audio_messages/Clock";
|
||||||
import { formatTimeLeft } from "../../../DateUtils";
|
import { formatTimeLeft } from "../../../DateUtils";
|
||||||
|
import Spinner from "../../../components/views/elements/Spinner";
|
||||||
|
|
||||||
interface VoiceBroadcastHeaderProps {
|
interface VoiceBroadcastHeaderProps {
|
||||||
live?: VoiceBroadcastLiveness;
|
live?: VoiceBroadcastLiveness;
|
||||||
|
@ -33,6 +34,7 @@ interface VoiceBroadcastHeaderProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
microphoneLabel?: string;
|
microphoneLabel?: string;
|
||||||
showBroadcast?: boolean;
|
showBroadcast?: boolean;
|
||||||
|
showBuffering?: boolean;
|
||||||
timeLeft?: number;
|
timeLeft?: number;
|
||||||
showClose?: boolean;
|
showClose?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -44,47 +46,55 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
||||||
room,
|
room,
|
||||||
microphoneLabel,
|
microphoneLabel,
|
||||||
showBroadcast = false,
|
showBroadcast = false,
|
||||||
|
showBuffering = false,
|
||||||
showClose = false,
|
showClose = false,
|
||||||
timeLeft,
|
timeLeft,
|
||||||
}) => {
|
}) => {
|
||||||
const broadcast = showBroadcast
|
const broadcast = showBroadcast && (
|
||||||
? <div className="mx_VoiceBroadcastHeader_line">
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
<LiveIcon className="mx_Icon mx_Icon_16" />
|
<LiveIcon className="mx_Icon mx_Icon_16" />
|
||||||
{ _t("Voice broadcast") }
|
{ _t("Voice broadcast") }
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
const liveBadge = live === "not-live"
|
const liveBadge = live !== "not-live" && (
|
||||||
? null
|
<LiveBadge grey={live === "grey"} />
|
||||||
: <LiveBadge grey={live === "grey"} />;
|
);
|
||||||
|
|
||||||
const closeButton = showClose
|
const closeButton = showClose && (
|
||||||
? <AccessibleButton onClick={onCloseClick}>
|
<AccessibleButton onClick={onCloseClick}>
|
||||||
<XIcon className="mx_Icon mx_Icon_16" />
|
<XIcon className="mx_Icon mx_Icon_16" />
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
const timeLeftLine = timeLeft
|
const timeLeftLine = timeLeft && (
|
||||||
? <div className="mx_VoiceBroadcastHeader_line">
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
<TimerIcon className="mx_Icon mx_Icon_16" />
|
<TimerIcon className="mx_Icon mx_Icon_16" />
|
||||||
<Clock formatFn={formatTimeLeft} seconds={timeLeft} />
|
<Clock formatFn={formatTimeLeft} seconds={timeLeft} />
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
|
const buffering = showBuffering && (
|
||||||
|
<div className="mx_VoiceBroadcastHeader_line">
|
||||||
|
<Spinner w={14} h={14} />
|
||||||
|
{ _t("Buffering…") }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const microphoneLineClasses = classNames({
|
const microphoneLineClasses = classNames({
|
||||||
mx_VoiceBroadcastHeader_line: true,
|
mx_VoiceBroadcastHeader_line: true,
|
||||||
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
|
["mx_VoiceBroadcastHeader_mic--clickable"]: onMicrophoneLineClick,
|
||||||
});
|
});
|
||||||
|
|
||||||
const microphoneLine = microphoneLabel
|
const microphoneLine = microphoneLabel && (
|
||||||
? <div
|
<div
|
||||||
className={microphoneLineClasses}
|
className={microphoneLineClasses}
|
||||||
onClick={onMicrophoneLineClick}
|
onClick={onMicrophoneLineClick}
|
||||||
>
|
>
|
||||||
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
|
<MicrophoneIcon className="mx_Icon mx_Icon_16" />
|
||||||
<span>{ microphoneLabel }</span>
|
<span>{ microphoneLabel }</span>
|
||||||
</div>
|
</div>
|
||||||
: null;
|
);
|
||||||
|
|
||||||
return <div className="mx_VoiceBroadcastHeader">
|
return <div className="mx_VoiceBroadcastHeader">
|
||||||
<RoomAvatar room={room} width={32} height={32} />
|
<RoomAvatar room={room} width={32} height={32} />
|
||||||
|
@ -95,6 +105,7 @@ export const VoiceBroadcastHeader: React.FC<VoiceBroadcastHeaderProps> = ({
|
||||||
{ microphoneLine }
|
{ microphoneLine }
|
||||||
{ timeLeftLine }
|
{ timeLeftLine }
|
||||||
{ broadcast }
|
{ broadcast }
|
||||||
|
{ buffering }
|
||||||
</div>
|
</div>
|
||||||
{ liveBadge }
|
{ liveBadge }
|
||||||
{ closeButton }
|
{ closeButton }
|
||||||
|
|
|
@ -23,7 +23,6 @@ import {
|
||||||
VoiceBroadcastPlayback,
|
VoiceBroadcastPlayback,
|
||||||
VoiceBroadcastPlaybackState,
|
VoiceBroadcastPlaybackState,
|
||||||
} from "../..";
|
} from "../..";
|
||||||
import Spinner from "../../../components/views/elements/Spinner";
|
|
||||||
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
|
import { useVoiceBroadcastPlayback } from "../../hooks/useVoiceBroadcastPlayback";
|
||||||
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
import { Icon as PlayIcon } from "../../../../res/img/element-icons/play.svg";
|
||||||
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
import { Icon as PauseIcon } from "../../../../res/img/element-icons/pause.svg";
|
||||||
|
@ -54,11 +53,6 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
toggle,
|
toggle,
|
||||||
} = useVoiceBroadcastPlayback(playback);
|
} = useVoiceBroadcastPlayback(playback);
|
||||||
|
|
||||||
let control: React.ReactNode;
|
|
||||||
|
|
||||||
if (playbackState === VoiceBroadcastPlaybackState.Buffering) {
|
|
||||||
control = <Spinner />;
|
|
||||||
} else {
|
|
||||||
let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>;
|
let controlIcon: React.FC<React.SVGProps<SVGSVGElement>>;
|
||||||
let controlLabel: string;
|
let controlLabel: string;
|
||||||
let className = "";
|
let className = "";
|
||||||
|
@ -74,19 +68,19 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
className = "mx_VoiceBroadcastControl-play";
|
className = "mx_VoiceBroadcastControl-play";
|
||||||
controlLabel = _t("resume voice broadcast");
|
controlLabel = _t("resume voice broadcast");
|
||||||
break;
|
break;
|
||||||
|
case VoiceBroadcastPlaybackState.Buffering:
|
||||||
case VoiceBroadcastPlaybackState.Playing:
|
case VoiceBroadcastPlaybackState.Playing:
|
||||||
controlIcon = PauseIcon;
|
controlIcon = PauseIcon;
|
||||||
controlLabel = _t("pause voice broadcast");
|
controlLabel = _t("pause voice broadcast");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
control = <VoiceBroadcastControl
|
const control = <VoiceBroadcastControl
|
||||||
className={className}
|
className={className}
|
||||||
label={controlLabel}
|
label={controlLabel}
|
||||||
icon={controlIcon}
|
icon={controlIcon}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
/>;
|
/>;
|
||||||
}
|
|
||||||
|
|
||||||
let seekBackwardButton: ReactElement | null = null;
|
let seekBackwardButton: ReactElement | null = null;
|
||||||
let seekForwardButton: ReactElement | null = null;
|
let seekForwardButton: ReactElement | null = null;
|
||||||
|
@ -124,7 +118,8 @@ export const VoiceBroadcastPlaybackBody: React.FC<VoiceBroadcastPlaybackBodyProp
|
||||||
live={liveness}
|
live={liveness}
|
||||||
microphoneLabel={sender?.name}
|
microphoneLabel={sender?.name}
|
||||||
room={room}
|
room={room}
|
||||||
showBroadcast={true}
|
showBroadcast={playbackState !== VoiceBroadcastPlaybackState.Buffering}
|
||||||
|
showBuffering={playbackState === VoiceBroadcastPlaybackState.Buffering}
|
||||||
/>
|
/>
|
||||||
<div className="mx_VoiceBroadcastBody_controls">
|
<div className="mx_VoiceBroadcastBody_controls">
|
||||||
{ seekBackwardButton }
|
{ seekBackwardButton }
|
||||||
|
|
|
@ -27,6 +27,13 @@ import {
|
||||||
export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
|
export const useVoiceBroadcastPlayback = (playback: VoiceBroadcastPlayback) => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(playback.infoEvent.getRoomId());
|
const room = client.getRoom(playback.infoEvent.getRoomId());
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
throw new Error(
|
||||||
|
`Voice Broadcast room not found (event ${playback.infoEvent.getId()})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const playbackToggle = () => {
|
const playbackToggle = () => {
|
||||||
playback.toggle();
|
playback.toggle();
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||||
|
|
||||||
import { IDestroyable } from "../../utils/IDestroyable";
|
import { IDestroyable } from "../../utils/IDestroyable";
|
||||||
|
import { VoiceBroadcastPlaybacksStore } from "../stores/VoiceBroadcastPlaybacksStore";
|
||||||
import { VoiceBroadcastRecordingsStore } from "../stores/VoiceBroadcastRecordingsStore";
|
import { VoiceBroadcastRecordingsStore } from "../stores/VoiceBroadcastRecordingsStore";
|
||||||
import { startNewVoiceBroadcastRecording } from "../utils/startNewVoiceBroadcastRecording";
|
import { startNewVoiceBroadcastRecording } from "../utils/startNewVoiceBroadcastRecording";
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ export class VoiceBroadcastPreRecording
|
||||||
public room: Room,
|
public room: Room,
|
||||||
public sender: RoomMember,
|
public sender: RoomMember,
|
||||||
private client: MatrixClient,
|
private client: MatrixClient,
|
||||||
|
private playbacksStore: VoiceBroadcastPlaybacksStore,
|
||||||
private recordingsStore: VoiceBroadcastRecordingsStore,
|
private recordingsStore: VoiceBroadcastRecordingsStore,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -43,6 +45,7 @@ export class VoiceBroadcastPreRecording
|
||||||
await startNewVoiceBroadcastRecording(
|
await startNewVoiceBroadcastRecording(
|
||||||
this.room,
|
this.room,
|
||||||
this.client,
|
this.client,
|
||||||
|
this.playbacksStore,
|
||||||
this.recordingsStore,
|
this.recordingsStore,
|
||||||
);
|
);
|
||||||
this.emit("dismiss", this);
|
this.emit("dismiss", this);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkVoiceBroadcastPreConditions,
|
checkVoiceBroadcastPreConditions,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastPreRecording,
|
VoiceBroadcastPreRecording,
|
||||||
VoiceBroadcastPreRecordingStore,
|
VoiceBroadcastPreRecordingStore,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
|
@ -26,6 +27,7 @@ import {
|
||||||
export const setUpVoiceBroadcastPreRecording = (
|
export const setUpVoiceBroadcastPreRecording = (
|
||||||
room: Room,
|
room: Room,
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
|
playbacksStore: VoiceBroadcastPlaybacksStore,
|
||||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||||
preRecordingStore: VoiceBroadcastPreRecordingStore,
|
preRecordingStore: VoiceBroadcastPreRecordingStore,
|
||||||
): VoiceBroadcastPreRecording | null => {
|
): VoiceBroadcastPreRecording | null => {
|
||||||
|
@ -39,7 +41,11 @@ export const setUpVoiceBroadcastPreRecording = (
|
||||||
const sender = room.getMember(userId);
|
const sender = room.getMember(userId);
|
||||||
if (!sender) return null;
|
if (!sender) return null;
|
||||||
|
|
||||||
const preRecording = new VoiceBroadcastPreRecording(room, sender, client, recordingsStore);
|
// pause and clear current playback (if any)
|
||||||
|
playbacksStore.getCurrent()?.pause();
|
||||||
|
playbacksStore.clearCurrent();
|
||||||
|
|
||||||
|
const preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||||
preRecordingStore.setCurrent(preRecording);
|
preRecordingStore.setCurrent(preRecording);
|
||||||
return preRecording;
|
return preRecording;
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
VoiceBroadcastRecording,
|
VoiceBroadcastRecording,
|
||||||
getChunkLength,
|
getChunkLength,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
} from "..";
|
} from "..";
|
||||||
import { checkVoiceBroadcastPreConditions } from "./checkVoiceBroadcastPreConditions";
|
import { checkVoiceBroadcastPreConditions } from "./checkVoiceBroadcastPreConditions";
|
||||||
|
|
||||||
|
@ -80,17 +81,23 @@ const startBroadcast = async (
|
||||||
/**
|
/**
|
||||||
* Starts a new Voice Broadcast Recording, if
|
* Starts a new Voice Broadcast Recording, if
|
||||||
* - the user has the permissions to do so in the room
|
* - the user has the permissions to do so in the room
|
||||||
|
* - the user is not already recording a voice broadcast
|
||||||
* - there is no other broadcast being recorded in the room, yet
|
* - there is no other broadcast being recorded in the room, yet
|
||||||
* Sends a voice_broadcast_info state event and waits for the event to actually appear in the room state.
|
* Sends a voice_broadcast_info state event and waits for the event to actually appear in the room state.
|
||||||
*/
|
*/
|
||||||
export const startNewVoiceBroadcastRecording = async (
|
export const startNewVoiceBroadcastRecording = async (
|
||||||
room: Room,
|
room: Room,
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
|
playbacksStore: VoiceBroadcastPlaybacksStore,
|
||||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||||
): Promise<VoiceBroadcastRecording | null> => {
|
): Promise<VoiceBroadcastRecording | null> => {
|
||||||
if (!checkVoiceBroadcastPreConditions(room, client, recordingsStore)) {
|
if (!checkVoiceBroadcastPreConditions(room, client, recordingsStore)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pause and clear current playback (if any)
|
||||||
|
playbacksStore.getCurrent()?.pause();
|
||||||
|
playbacksStore.clearCurrent();
|
||||||
|
|
||||||
return startBroadcast(room, client, recordingsStore);
|
return startBroadcast(room, client, recordingsStore);
|
||||||
};
|
};
|
||||||
|
|
|
@ -91,7 +91,7 @@ describe("ContentMessages", () => {
|
||||||
Object.defineProperty(global.Image.prototype, 'src', {
|
Object.defineProperty(global.Image.prototype, 'src', {
|
||||||
// Define the property setter
|
// Define the property setter
|
||||||
set(src) {
|
set(src) {
|
||||||
setTimeout(() => this.onload());
|
window.setTimeout(() => this.onload());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Object.defineProperty(global.Image.prototype, 'height', {
|
Object.defineProperty(global.Image.prototype, 'height', {
|
||||||
|
|
|
@ -352,7 +352,7 @@ describe('<LocationShareMenu />', () => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
|
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
|
||||||
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
|
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
|
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,7 +54,7 @@ const encryptedGroupRule = { "conditions": [{ "kind": "event_match", "key": "typ
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
const pushRules: IPushRules = { "global": { "underride": [{ "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.call.invite" }], "actions": ["notify", { "set_tweak": "sound", "value": "ring" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.call", "default": true, "enabled": true }, oneToOneRule, encryptedOneToOneRule, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.message" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.message", "default": true, "enabled": true }, encryptedGroupRule, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "im.vector.modular.widgets" }, { "kind": "event_match", "key": "content.type", "pattern": "jitsi" }, { "kind": "event_match", "key": "state_key", "pattern": "*" }], "actions": ["notify", { "set_tweak": "highlight", "value": false }], "rule_id": ".im.vector.jitsi", "default": true, "enabled": true }], "sender": [], "room": [{ "actions": ["dont_notify"], "rule_id": "!zJPyWqpMorfCcWObge:matrix.org", "default": false, "enabled": true }], "content": [{ "actions": ["notify", { "set_tweak": "highlight", "value": false }], "pattern": "banana", "rule_id": "banana", "default": false, "enabled": true }, { "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" }], "pattern": "kadev1", "rule_id": ".m.rule.contains_user_name", "default": true, "enabled": true }], "override": [{ "conditions": [], "actions": ["dont_notify"], "rule_id": ".m.rule.master", "default": true, "enabled": false }, { "conditions": [{ "kind": "event_match", "key": "content.msgtype", "pattern": "m.notice" }], "actions": ["dont_notify"], "rule_id": ".m.rule.suppress_notices", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.member" }, { "kind": "event_match", "key": "content.membership", "pattern": "invite" }, { "kind": "event_match", "key": "state_key", "pattern": "@kadev1:matrix.org" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.invite_for_me", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.member" }], "actions": ["dont_notify"], "rule_id": ".m.rule.member_event", "default": true, "enabled": true }, { "conditions": [{ "kind": "contains_display_name" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" }], "rule_id": ".m.rule.contains_display_name", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "content.body", "pattern": "@room" }, { "kind": "sender_notification_permission", "key": "room" }], "actions": ["notify", { "set_tweak": "highlight", "value": true }], "rule_id": ".m.rule.roomnotif", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.tombstone" }, { "kind": "event_match", "key": "state_key", "pattern": "" }], "actions": ["notify", { "set_tweak": "highlight", "value": true }], "rule_id": ".m.rule.tombstone", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.reaction" }], "actions": ["dont_notify"], "rule_id": ".m.rule.reaction", "default": true, "enabled": true }] }, "device": {} } as IPushRules;
|
const pushRules: IPushRules = { "global": { "underride": [{ "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.call.invite" }], "actions": ["notify", { "set_tweak": "sound", "value": "ring" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.call", "default": true, "enabled": true }, oneToOneRule, encryptedOneToOneRule, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.message" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.message", "default": true, "enabled": true }, encryptedGroupRule, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "im.vector.modular.widgets" }, { "kind": "event_match", "key": "content.type", "pattern": "jitsi" }, { "kind": "event_match", "key": "state_key", "pattern": "*" }], "actions": ["notify", { "set_tweak": "highlight", "value": false }], "rule_id": ".im.vector.jitsi", "default": true, "enabled": true }], "sender": [], "room": [{ "actions": ["dont_notify"], "rule_id": "!zJPyWqpMorfCcWObge:matrix.org", "default": false, "enabled": true }], "content": [{ "actions": ["notify", { "set_tweak": "highlight", "value": false }], "pattern": "banana", "rule_id": "banana", "default": false, "enabled": true }, { "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" }], "pattern": "kadev1", "rule_id": ".m.rule.contains_user_name", "default": true, "enabled": true }], "override": [{ "conditions": [], "actions": ["dont_notify"], "rule_id": ".m.rule.master", "default": true, "enabled": false }, { "conditions": [{ "kind": "event_match", "key": "content.msgtype", "pattern": "m.notice" }], "actions": ["dont_notify"], "rule_id": ".m.rule.suppress_notices", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.member" }, { "kind": "event_match", "key": "content.membership", "pattern": "invite" }, { "kind": "event_match", "key": "state_key", "pattern": "@kadev1:matrix.org" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight", "value": false }], "rule_id": ".m.rule.invite_for_me", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.member" }], "actions": ["dont_notify"], "rule_id": ".m.rule.member_event", "default": true, "enabled": true }, { "conditions": [{ "kind": "contains_display_name" }], "actions": ["notify", { "set_tweak": "sound", "value": "default" }, { "set_tweak": "highlight" }], "rule_id": ".m.rule.contains_display_name", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "content.body", "pattern": "@room" }, { "kind": "sender_notification_permission", "key": "room" }], "actions": ["notify", { "set_tweak": "highlight", "value": true }], "rule_id": ".m.rule.roomnotif", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.room.tombstone" }, { "kind": "event_match", "key": "state_key", "pattern": "" }], "actions": ["notify", { "set_tweak": "highlight", "value": true }], "rule_id": ".m.rule.tombstone", "default": true, "enabled": true }, { "conditions": [{ "kind": "event_match", "key": "type", "pattern": "m.reaction" }], "actions": ["dont_notify"], "rule_id": ".m.rule.reaction", "default": true, "enabled": true }] }, "device": {} } as IPushRules;
|
||||||
|
|
||||||
const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
const flushPromises = async () => await new Promise(resolve => window.setTimeout(resolve));
|
||||||
|
|
||||||
describe('<Notifications />', () => {
|
describe('<Notifications />', () => {
|
||||||
const getComponent = () => render(<Notifications />);
|
const getComponent = () => render(<Notifications />);
|
||||||
|
|
|
@ -184,6 +184,7 @@ describe("PipView", () => {
|
||||||
room,
|
room,
|
||||||
alice,
|
alice,
|
||||||
client,
|
client,
|
||||||
|
voiceBroadcastPlaybacksStore,
|
||||||
voiceBroadcastRecordingsStore,
|
voiceBroadcastRecordingsStore,
|
||||||
);
|
);
|
||||||
voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording);
|
voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording);
|
||||||
|
@ -271,6 +272,19 @@ describe("PipView", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when there is a voice broadcast playback and pre-recording", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
startVoiceBroadcastPlayback(room);
|
||||||
|
setUpVoiceBroadcastPreRecording();
|
||||||
|
renderPip();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the voice broadcast pre-recording PiP", () => {
|
||||||
|
// check for the „Go live“ button
|
||||||
|
expect(screen.queryByText("Go live")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("when there is a voice broadcast pre-recording", () => {
|
describe("when there is a voice broadcast pre-recording", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setUpVoiceBroadcastPreRecording();
|
setUpVoiceBroadcastPreRecording();
|
||||||
|
|
|
@ -21,7 +21,7 @@ import fetch from 'node-fetch';
|
||||||
// jest 27 removes setImmediate from jsdom
|
// jest 27 removes setImmediate from jsdom
|
||||||
// polyfill until setImmediate use in client can be removed
|
// polyfill until setImmediate use in client can be removed
|
||||||
// @ts-ignore - we know the contract is wrong. That's why we're stubbing it.
|
// @ts-ignore - we know the contract is wrong. That's why we're stubbing it.
|
||||||
global.setImmediate = callback => setTimeout(callback, 0);
|
global.setImmediate = callback => window.setTimeout(callback, 0);
|
||||||
|
|
||||||
// Stub ResizeObserver
|
// Stub ResizeObserver
|
||||||
// @ts-ignore - we know it's a duplicate (that's why we're stubbing it)
|
// @ts-ignore - we know it's a duplicate (that's why we're stubbing it)
|
||||||
|
|
|
@ -179,7 +179,7 @@ export const watchPositionMockImplementation = (delays: number[], errorCodes: nu
|
||||||
let totalDelay = 0;
|
let totalDelay = 0;
|
||||||
delays.map((delayMs, index) => {
|
delays.map((delayMs, index) => {
|
||||||
totalDelay += delayMs;
|
totalDelay += delayMs;
|
||||||
const timeout = setTimeout(() => {
|
const timeout = window.setTimeout(() => {
|
||||||
if (errorCodes[index]) {
|
if (errorCodes[index]) {
|
||||||
error(getMockGeolocationPositionError(errorCodes[index], 'error message'));
|
error(getMockGeolocationPositionError(errorCodes[index], 'error message'));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -48,7 +48,7 @@ export function untilDispatch(
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
// set a timeout handler if needed
|
// set a timeout handler if needed
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = window.setTimeout(() => {
|
||||||
if (!fulfilled) {
|
if (!fulfilled) {
|
||||||
reject(new Error(`untilDispatch: timed out at ${callerLine}`));
|
reject(new Error(`untilDispatch: timed out at ${callerLine}`));
|
||||||
fulfilled = true;
|
fulfilled = true;
|
||||||
|
@ -92,7 +92,7 @@ export function untilEmission(
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
// set a timeout handler if needed
|
// set a timeout handler if needed
|
||||||
if (timeout > 0) {
|
if (timeout > 0) {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = window.setTimeout(() => {
|
||||||
if (!fulfilled) {
|
if (!fulfilled) {
|
||||||
reject(new Error(`untilEmission: timed out at ${callerLine}`));
|
reject(new Error(`untilEmission: timed out at ${callerLine}`));
|
||||||
fulfilled = true;
|
fulfilled = true;
|
||||||
|
@ -134,7 +134,7 @@ const findByTagAndAttr = (attr: string) =>
|
||||||
|
|
||||||
export const findByTagAndTestId = findByTagAndAttr('data-test-id');
|
export const findByTagAndTestId = findByTagAndAttr('data-test-id');
|
||||||
|
|
||||||
export const flushPromises = async () => await new Promise(resolve => setTimeout(resolve));
|
export const flushPromises = async () => await new Promise(resolve => window.setTimeout(resolve));
|
||||||
|
|
||||||
// with jest's modern fake timers process.nextTick is also mocked,
|
// with jest's modern fake timers process.nextTick is also mocked,
|
||||||
// flushing promises in the normal way then waits for some advancement
|
// flushing promises in the normal way then waits for some advancement
|
||||||
|
|
|
@ -35,12 +35,17 @@ describe("VoiceBroadcastHeader", () => {
|
||||||
const sender = new RoomMember(roomId, userId);
|
const sender = new RoomMember(roomId, userId);
|
||||||
let container: Container;
|
let container: Container;
|
||||||
|
|
||||||
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast: boolean = undefined): RenderResult => {
|
const renderHeader = (
|
||||||
|
live: VoiceBroadcastLiveness,
|
||||||
|
showBroadcast?: boolean,
|
||||||
|
buffering?: boolean,
|
||||||
|
): RenderResult => {
|
||||||
return render(<VoiceBroadcastHeader
|
return render(<VoiceBroadcastHeader
|
||||||
live={live}
|
live={live}
|
||||||
microphoneLabel={sender.name}
|
microphoneLabel={sender.name}
|
||||||
room={room}
|
room={room}
|
||||||
showBroadcast={showBroadcast}
|
showBroadcast={showBroadcast}
|
||||||
|
showBuffering={buffering}
|
||||||
/>);
|
/>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,6 +56,16 @@ describe("VoiceBroadcastHeader", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when rendering a live broadcast header with broadcast info", () => {
|
describe("when rendering a live broadcast header with broadcast info", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
container = renderHeader("live", true, true).container;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render the header with a red live badge", () => {
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when rendering a buffering live broadcast header with broadcast info", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
container = renderHeader("live", true).container;
|
container = renderHeader("live", true).container;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,55 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header with broadcast info should render the header with a red live badge 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="room-avatar"
|
||||||
|
>
|
||||||
|
room avatar:
|
||||||
|
!room:example.com
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_content"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_room"
|
||||||
|
>
|
||||||
|
!room:example.com
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Icon mx_Icon_16"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
test user
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Icon mx_Icon_16"
|
||||||
|
/>
|
||||||
|
Voice broadcast
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_LiveBadge"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Icon mx_Icon_16"
|
||||||
|
/>
|
||||||
|
Live
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = `
|
exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -87,6 +137,22 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
|
||||||
/>
|
/>
|
||||||
Voice broadcast
|
Voice broadcast
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_Spinner"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Loading..."
|
||||||
|
class="mx_Spinner_icon"
|
||||||
|
data-testid="spinner"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
Buffering…
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_LiveBadge"
|
class="mx_LiveBadge"
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { act, render, RenderResult, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastPreRecording,
|
VoiceBroadcastPreRecording,
|
||||||
VoiceBroadcastPreRecordingPip,
|
VoiceBroadcastPreRecordingPip,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
|
@ -42,6 +43,7 @@ jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||||
describe("VoiceBroadcastPreRecordingPip", () => {
|
describe("VoiceBroadcastPreRecordingPip", () => {
|
||||||
let renderResult: RenderResult;
|
let renderResult: RenderResult;
|
||||||
let preRecording: VoiceBroadcastPreRecording;
|
let preRecording: VoiceBroadcastPreRecording;
|
||||||
|
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
|
@ -51,6 +53,7 @@ describe("VoiceBroadcastPreRecordingPip", () => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
room = new Room("!room@example.com", client, client.getUserId() || "");
|
room = new Room("!room@example.com", client, client.getUserId() || "");
|
||||||
sender = new RoomMember(room.roomId, client.getUserId() || "");
|
sender = new RoomMember(room.roomId, client.getUserId() || "");
|
||||||
|
playbacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||||
mocked(requestMediaPermissions).mockReturnValue(new Promise<MediaStream>((r) => {
|
mocked(requestMediaPermissions).mockReturnValue(new Promise<MediaStream>((r) => {
|
||||||
r({
|
r({
|
||||||
|
@ -76,6 +79,7 @@ describe("VoiceBroadcastPreRecordingPip", () => {
|
||||||
room,
|
room,
|
||||||
sender,
|
sender,
|
||||||
client,
|
client,
|
||||||
|
playbacksStore,
|
||||||
recordingsStore,
|
recordingsStore,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -252,9 +252,17 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||||
class="mx_VoiceBroadcastHeader_line"
|
class="mx_VoiceBroadcastHeader_line"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_Icon mx_Icon_16"
|
class="mx_Spinner"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="Loading..."
|
||||||
|
class="mx_Spinner_icon"
|
||||||
|
data-testid="spinner"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: 14px; height: 14px;"
|
||||||
/>
|
/>
|
||||||
Voice broadcast
|
</div>
|
||||||
|
Buffering…
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -280,14 +288,13 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mx_Spinner"
|
aria-label="pause voice broadcast"
|
||||||
|
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label="Loading..."
|
class="mx_Icon mx_Icon_16"
|
||||||
class="mx_Spinner_icon"
|
|
||||||
data-testid="spinner"
|
|
||||||
role="progressbar"
|
|
||||||
style="width: 32px; height: 32px;"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
startNewVoiceBroadcastRecording,
|
startNewVoiceBroadcastRecording,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastPreRecording,
|
VoiceBroadcastPreRecording,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
} from "../../../src/voice-broadcast";
|
} from "../../../src/voice-broadcast";
|
||||||
|
@ -30,6 +31,7 @@ describe("VoiceBroadcastPreRecording", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let sender: RoomMember;
|
let sender: RoomMember;
|
||||||
|
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||||
let preRecording: VoiceBroadcastPreRecording;
|
let preRecording: VoiceBroadcastPreRecording;
|
||||||
let onDismiss: (voiceBroadcastPreRecording: VoiceBroadcastPreRecording) => void;
|
let onDismiss: (voiceBroadcastPreRecording: VoiceBroadcastPreRecording) => void;
|
||||||
|
@ -38,12 +40,13 @@ describe("VoiceBroadcastPreRecording", () => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
room = new Room(roomId, client, client.getUserId() || "");
|
room = new Room(roomId, client, client.getUserId() || "");
|
||||||
sender = new RoomMember(roomId, client.getUserId() || "");
|
sender = new RoomMember(roomId, client.getUserId() || "");
|
||||||
|
playbacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
onDismiss = jest.fn();
|
onDismiss = jest.fn();
|
||||||
preRecording = new VoiceBroadcastPreRecording(room, sender, client, recordingsStore);
|
preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||||
preRecording.on("dismiss", onDismiss);
|
preRecording.on("dismiss", onDismiss);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,6 +59,7 @@ describe("VoiceBroadcastPreRecording", () => {
|
||||||
expect(startNewVoiceBroadcastRecording).toHaveBeenCalledWith(
|
expect(startNewVoiceBroadcastRecording).toHaveBeenCalledWith(
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
|
playbacksStore,
|
||||||
recordingsStore,
|
recordingsStore,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { mocked } from "jest-mock";
|
||||||
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastPreRecording,
|
VoiceBroadcastPreRecording,
|
||||||
VoiceBroadcastPreRecordingStore,
|
VoiceBroadcastPreRecordingStore,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
|
@ -31,6 +32,7 @@ describe("VoiceBroadcastPreRecordingStore", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let sender: RoomMember;
|
let sender: RoomMember;
|
||||||
|
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||||
let store: VoiceBroadcastPreRecordingStore;
|
let store: VoiceBroadcastPreRecordingStore;
|
||||||
let preRecording1: VoiceBroadcastPreRecording;
|
let preRecording1: VoiceBroadcastPreRecording;
|
||||||
|
@ -39,6 +41,7 @@ describe("VoiceBroadcastPreRecordingStore", () => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
room = new Room(roomId, client, client.getUserId() || "");
|
room = new Room(roomId, client, client.getUserId() || "");
|
||||||
sender = new RoomMember(roomId, client.getUserId() || "");
|
sender = new RoomMember(roomId, client.getUserId() || "");
|
||||||
|
playbacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +49,7 @@ describe("VoiceBroadcastPreRecordingStore", () => {
|
||||||
store = new VoiceBroadcastPreRecordingStore();
|
store = new VoiceBroadcastPreRecordingStore();
|
||||||
jest.spyOn(store, "emit");
|
jest.spyOn(store, "emit");
|
||||||
jest.spyOn(store, "removeAllListeners");
|
jest.spyOn(store, "removeAllListeners");
|
||||||
preRecording1 = new VoiceBroadcastPreRecording(room, sender, client, recordingsStore);
|
preRecording1 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||||
jest.spyOn(preRecording1, "off");
|
jest.spyOn(preRecording1, "off");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,7 +120,7 @@ describe("VoiceBroadcastPreRecordingStore", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mocked(store.emit).mockClear();
|
mocked(store.emit).mockClear();
|
||||||
mocked(preRecording1.off).mockClear();
|
mocked(preRecording1.off).mockClear();
|
||||||
preRecording2 = new VoiceBroadcastPreRecording(room, sender, client, recordingsStore);
|
preRecording2 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||||
store.setCurrent(preRecording2);
|
store.setCurrent(preRecording2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,20 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
checkVoiceBroadcastPreConditions,
|
checkVoiceBroadcastPreConditions,
|
||||||
|
VoiceBroadcastInfoState,
|
||||||
|
VoiceBroadcastPlayback,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
VoiceBroadcastPreRecording,
|
VoiceBroadcastPreRecording,
|
||||||
VoiceBroadcastPreRecordingStore,
|
VoiceBroadcastPreRecordingStore,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
} from "../../../src/voice-broadcast";
|
} from "../../../src/voice-broadcast";
|
||||||
import { setUpVoiceBroadcastPreRecording } from "../../../src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording";
|
import { setUpVoiceBroadcastPreRecording } from "../../../src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording";
|
||||||
import { mkRoomMemberJoinEvent, stubClient } from "../../test-utils";
|
import { mkRoomMemberJoinEvent, stubClient } from "../../test-utils";
|
||||||
|
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||||
|
|
||||||
jest.mock("../../../src/voice-broadcast/utils/checkVoiceBroadcastPreConditions");
|
jest.mock("../../../src/voice-broadcast/utils/checkVoiceBroadcastPreConditions");
|
||||||
|
|
||||||
|
@ -34,11 +38,20 @@ describe("setUpVoiceBroadcastPreRecording", () => {
|
||||||
let userId: string;
|
let userId: string;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let preRecordingStore: VoiceBroadcastPreRecordingStore;
|
let preRecordingStore: VoiceBroadcastPreRecordingStore;
|
||||||
|
let infoEvent: MatrixEvent;
|
||||||
|
let playback: VoiceBroadcastPlayback;
|
||||||
|
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||||
|
|
||||||
const itShouldReturnNull = () => {
|
const itShouldReturnNull = () => {
|
||||||
it("should return null", () => {
|
it("should return null", () => {
|
||||||
expect(setUpVoiceBroadcastPreRecording(room, client, recordingsStore, preRecordingStore)).toBeNull();
|
expect(setUpVoiceBroadcastPreRecording(
|
||||||
|
room,
|
||||||
|
client,
|
||||||
|
playbacksStore,
|
||||||
|
recordingsStore,
|
||||||
|
preRecordingStore,
|
||||||
|
)).toBeNull();
|
||||||
expect(checkVoiceBroadcastPreConditions).toHaveBeenCalledWith(room, client, recordingsStore);
|
expect(checkVoiceBroadcastPreConditions).toHaveBeenCalledWith(room, client, recordingsStore);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -51,7 +64,16 @@ describe("setUpVoiceBroadcastPreRecording", () => {
|
||||||
userId = clientUserId;
|
userId = clientUserId;
|
||||||
|
|
||||||
room = new Room(roomId, client, userId);
|
room = new Room(roomId, client, userId);
|
||||||
|
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||||
|
roomId,
|
||||||
|
VoiceBroadcastInfoState.Started,
|
||||||
|
client.getUserId()!,
|
||||||
|
client.getDeviceId()!,
|
||||||
|
);
|
||||||
preRecordingStore = new VoiceBroadcastPreRecordingStore();
|
preRecordingStore = new VoiceBroadcastPreRecordingStore();
|
||||||
|
playback = new VoiceBroadcastPlayback(infoEvent, client);
|
||||||
|
jest.spyOn(playback, "pause");
|
||||||
|
playbacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -85,15 +107,25 @@ describe("setUpVoiceBroadcastPreRecording", () => {
|
||||||
itShouldReturnNull();
|
itShouldReturnNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("and there is a room member", () => {
|
describe("and there is a room member and listening to another broadcast", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
playbacksStore.setCurrent(playback);
|
||||||
room.currentState.setStateEvents([
|
room.currentState.setStateEvents([
|
||||||
mkRoomMemberJoinEvent(userId, roomId),
|
mkRoomMemberJoinEvent(userId, roomId),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a voice broadcast pre-recording", () => {
|
it("should pause the current playback and create a voice broadcast pre-recording", () => {
|
||||||
const result = setUpVoiceBroadcastPreRecording(room, client, recordingsStore, preRecordingStore);
|
const result = setUpVoiceBroadcastPreRecording(
|
||||||
|
room,
|
||||||
|
client,
|
||||||
|
playbacksStore,
|
||||||
|
recordingsStore,
|
||||||
|
preRecordingStore,
|
||||||
|
);
|
||||||
|
expect(playback.pause).toHaveBeenCalled();
|
||||||
|
expect(playbacksStore.getCurrent()).toBeNull();
|
||||||
|
|
||||||
expect(checkVoiceBroadcastPreConditions).toHaveBeenCalledWith(room, client, recordingsStore);
|
expect(checkVoiceBroadcastPreConditions).toHaveBeenCalledWith(room, client, recordingsStore);
|
||||||
expect(result).toBeInstanceOf(VoiceBroadcastPreRecording);
|
expect(result).toBeInstanceOf(VoiceBroadcastPreRecording);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { EventType, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
import { EventType, ISendEventResponse, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import Modal from "../../../src/Modal";
|
import Modal from "../../../src/Modal";
|
||||||
import {
|
import {
|
||||||
|
@ -24,6 +24,8 @@ import {
|
||||||
VoiceBroadcastInfoState,
|
VoiceBroadcastInfoState,
|
||||||
VoiceBroadcastRecordingsStore,
|
VoiceBroadcastRecordingsStore,
|
||||||
VoiceBroadcastRecording,
|
VoiceBroadcastRecording,
|
||||||
|
VoiceBroadcastPlaybacksStore,
|
||||||
|
VoiceBroadcastPlayback,
|
||||||
} from "../../../src/voice-broadcast";
|
} from "../../../src/voice-broadcast";
|
||||||
import { mkEvent, stubClient } from "../../test-utils";
|
import { mkEvent, stubClient } from "../../test-utils";
|
||||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||||
|
@ -38,6 +40,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
const roomId = "!room:example.com";
|
const roomId = "!room:example.com";
|
||||||
const otherUserId = "@other:example.com";
|
const otherUserId = "@other:example.com";
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
|
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
let infoEvent: MatrixEvent;
|
let infoEvent: MatrixEvent;
|
||||||
|
@ -46,45 +49,50 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
client = stubClient();
|
client = stubClient();
|
||||||
room = new Room(roomId, client, client.getUserId());
|
room = new Room(roomId, client, client.getUserId()!);
|
||||||
jest.spyOn(room.currentState, "maySendStateEvent");
|
jest.spyOn(room.currentState, "maySendStateEvent");
|
||||||
|
|
||||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||||
if (getRoomId === roomId) {
|
if (getRoomId === roomId) {
|
||||||
return room;
|
return room;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
mocked(client.sendStateEvent).mockImplementation((
|
mocked(client.sendStateEvent).mockImplementation((
|
||||||
sendRoomId: string,
|
sendRoomId: string,
|
||||||
eventType: string,
|
eventType: string,
|
||||||
_content: any,
|
content: any,
|
||||||
_stateKey: string,
|
stateKey: string,
|
||||||
) => {
|
): Promise<ISendEventResponse> => {
|
||||||
if (sendRoomId === roomId && eventType === VoiceBroadcastInfoEventType) {
|
if (sendRoomId === roomId && eventType === VoiceBroadcastInfoEventType) {
|
||||||
return Promise.resolve({ event_id: infoEvent.getId() });
|
return Promise.resolve({ event_id: infoEvent.getId()! });
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
recordingsStore = {
|
throw new Error("Unexpected sendStateEvent call");
|
||||||
setCurrent: jest.fn(),
|
});
|
||||||
getCurrent: jest.fn(),
|
|
||||||
} as unknown as VoiceBroadcastRecordingsStore;
|
|
||||||
|
|
||||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||||
roomId,
|
roomId,
|
||||||
VoiceBroadcastInfoState.Started,
|
VoiceBroadcastInfoState.Started,
|
||||||
client.getUserId(),
|
client.getUserId()!,
|
||||||
client.getDeviceId(),
|
client.getDeviceId()!,
|
||||||
);
|
);
|
||||||
otherEvent = mkEvent({
|
otherEvent = mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
type: EventType.RoomMember,
|
type: EventType.RoomMember,
|
||||||
content: {},
|
content: {},
|
||||||
user: client.getUserId(),
|
user: client.getUserId()!,
|
||||||
room: roomId,
|
room: roomId,
|
||||||
skey: "",
|
skey: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
playbacksStore = new VoiceBroadcastPlaybacksStore();
|
||||||
|
recordingsStore = {
|
||||||
|
setCurrent: jest.fn(),
|
||||||
|
getCurrent: jest.fn(),
|
||||||
|
} as unknown as VoiceBroadcastRecordingsStore;
|
||||||
|
|
||||||
mocked(VoiceBroadcastRecording).mockImplementation((
|
mocked(VoiceBroadcastRecording).mockImplementation((
|
||||||
infoEvent: MatrixEvent,
|
infoEvent: MatrixEvent,
|
||||||
client: MatrixClient,
|
client: MatrixClient,
|
||||||
|
@ -106,22 +114,35 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
mocked(room.currentState.maySendStateEvent).mockReturnValue(true);
|
mocked(room.currentState.maySendStateEvent).mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when there currently is no other broadcast", () => {
|
describe("when currently listening to a broadcast and there is no recording", () => {
|
||||||
it("should create a new Voice Broadcast", async () => {
|
let playback: VoiceBroadcastPlayback;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
playback = new VoiceBroadcastPlayback(infoEvent, client);
|
||||||
|
jest.spyOn(playback, "pause");
|
||||||
|
playbacksStore.setCurrent(playback);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should stop listen to the current broadcast and create a new recording", async () => {
|
||||||
mocked(client.sendStateEvent).mockImplementation(async (
|
mocked(client.sendStateEvent).mockImplementation(async (
|
||||||
_roomId: string,
|
_roomId: string,
|
||||||
_eventType: string,
|
_eventType: string,
|
||||||
_content: any,
|
_content: any,
|
||||||
_stateKey = "",
|
_stateKey = "",
|
||||||
) => {
|
): Promise<ISendEventResponse> => {
|
||||||
setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
// emit state events after resolving the promise
|
// emit state events after resolving the promise
|
||||||
room.currentState.setStateEvents([otherEvent]);
|
room.currentState.setStateEvents([otherEvent]);
|
||||||
room.currentState.setStateEvents([infoEvent]);
|
room.currentState.setStateEvents([infoEvent]);
|
||||||
}, 0);
|
}, 0);
|
||||||
return { event_id: infoEvent.getId() };
|
return { event_id: infoEvent.getId()! };
|
||||||
});
|
});
|
||||||
const recording = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
const recording = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||||
|
expect(recording).not.toBeNull();
|
||||||
|
|
||||||
|
// expect to stop and clear the current playback
|
||||||
|
expect(playback.pause).toHaveBeenCalled();
|
||||||
|
expect(playbacksStore.getCurrent()).toBeNull();
|
||||||
|
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||||
roomId,
|
roomId,
|
||||||
|
@ -133,8 +154,8 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
},
|
},
|
||||||
client.getUserId(),
|
client.getUserId(),
|
||||||
);
|
);
|
||||||
expect(recording.infoEvent).toBe(infoEvent);
|
expect(recording!.infoEvent).toBe(infoEvent);
|
||||||
expect(recording.start).toHaveBeenCalled();
|
expect(recording!.start).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,7 +165,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
new VoiceBroadcastRecording(infoEvent, client),
|
new VoiceBroadcastRecording(infoEvent, client),
|
||||||
);
|
);
|
||||||
|
|
||||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not start a voice broadcast", () => {
|
it("should not start a voice broadcast", () => {
|
||||||
|
@ -162,12 +183,12 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
mkVoiceBroadcastInfoStateEvent(
|
mkVoiceBroadcastInfoStateEvent(
|
||||||
roomId,
|
roomId,
|
||||||
VoiceBroadcastInfoState.Resumed,
|
VoiceBroadcastInfoState.Resumed,
|
||||||
client.getUserId(),
|
client.getUserId()!,
|
||||||
client.getDeviceId(),
|
client.getDeviceId()!,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not start a voice broadcast", () => {
|
it("should not start a voice broadcast", () => {
|
||||||
|
@ -190,7 +211,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not start a voice broadcast", () => {
|
it("should not start a voice broadcast", () => {
|
||||||
|
@ -206,7 +227,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
||||||
describe("when the current user is not allowed to send voice broadcast info state events", () => {
|
describe("when the current user is not allowed to send voice broadcast info state events", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mocked(room.currentState.maySendStateEvent).mockReturnValue(false);
|
mocked(room.currentState.maySendStateEvent).mockReturnValue(false);
|
||||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not start a voice broadcast", () => {
|
it("should not start a voice broadcast", () => {
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -2451,10 +2451,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.26.tgz#239e19f8b4ea1a9eb710528061c1d733dc561996"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.26.tgz#239e19f8b4ea1a9eb710528061c1d733dc561996"
|
||||||
integrity sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==
|
integrity sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==
|
||||||
|
|
||||||
"@types/node@^14.18.28":
|
"@types/node@^16":
|
||||||
version "14.18.28"
|
version "16.18.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.28.tgz#ddb82da2fff476a8e827e8773c84c19d9c235278"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
|
||||||
integrity sha512-CK2fnrQlIgKlCV3N2kM+Gznb5USlwA1KFX3rJVHmgVk6NJxFPuQ86pAcvKnu37IA4BGlSRz7sEE1lHL1aLZ/eQ==
|
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
|
@ -9469,10 +9469,10 @@ typedarray-to-buffer@^3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
typescript@4.8.4:
|
typescript@4.9.3:
|
||||||
version "4.8.4"
|
version "4.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db"
|
||||||
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==
|
||||||
|
|
||||||
ua-parser-js@^0.7.30:
|
ua-parser-js@^0.7.30:
|
||||||
version "0.7.31"
|
version "0.7.31"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue