Merge pull request #5314 from matrix-org/t3chguy/ts/3

Round of Typescript conversions
This commit is contained in:
Michael Telatynski 2020-10-14 11:15:30 +01:00 committed by GitHub
commit 12be6c34da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 253 additions and 242 deletions

View file

@ -32,6 +32,8 @@ import type {Renderer} from "react-dom";
import RightPanelStore from "../stores/RightPanelStore"; import RightPanelStore from "../stores/RightPanelStore";
import WidgetStore from "../stores/WidgetStore"; import WidgetStore from "../stores/WidgetStore";
import CallHandler from "../CallHandler"; import CallHandler from "../CallHandler";
import {Analytics} from "../Analytics";
import UserActivity from "../UserActivity";
declare global { declare global {
interface Window { interface Window {
@ -56,6 +58,8 @@ declare global {
mxRightPanelStore: RightPanelStore; mxRightPanelStore: RightPanelStore;
mxWidgetStore: WidgetStore; mxWidgetStore: WidgetStore;
mxCallHandler: CallHandler; mxCallHandler: CallHandler;
mxAnalytics: Analytics;
mxUserActivity: UserActivity;
} }
interface Document { interface Document {

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { getCurrentLanguage, _t, _td } from './languageHandler'; import {getCurrentLanguage, _t, _td, IVariables} from './languageHandler';
import PlatformPeg from './PlatformPeg'; import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig'; import SdkConfig from './SdkConfig';
import Modal from './Modal'; import Modal from './Modal';
@ -27,7 +27,7 @@ const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password
const hashVarRegex = /#\/(group|room|user)\/.*$/; const hashVarRegex = /#\/(group|room|user)\/.*$/;
// Remove all but the first item in the hash path. Redact unexpected hashes. // Remove all but the first item in the hash path. Redact unexpected hashes.
function getRedactedHash(hash) { function getRedactedHash(hash: string): string {
// Don't leak URLs we aren't expecting - they could contain tokens/PII // Don't leak URLs we aren't expecting - they could contain tokens/PII
const match = hashRegex.exec(hash); const match = hashRegex.exec(hash);
if (!match) { if (!match) {
@ -44,7 +44,7 @@ function getRedactedHash(hash) {
// Return the current origin, path and hash separated with a `/`. This does // Return the current origin, path and hash separated with a `/`. This does
// not include query parameters. // not include query parameters.
function getRedactedUrl() { function getRedactedUrl(): string {
const { origin, hash } = window.location; const { origin, hash } = window.location;
let { pathname } = window.location; let { pathname } = window.location;
@ -56,7 +56,25 @@ function getRedactedUrl() {
return origin + pathname + getRedactedHash(hash); return origin + pathname + getRedactedHash(hash);
} }
const customVariables = { interface IData {
/* eslint-disable camelcase */
gt_ms?: string;
e_c?: string;
e_a?: string;
e_n?: string;
e_v?: string;
ping?: string;
/* eslint-enable camelcase */
}
interface IVariable {
id: number;
expl: string; // explanation
example: string; // example value
getTextVariables?(): IVariables; // object to pass as 2nd argument to `_t`
}
const customVariables: Record<string, IVariable> = {
// The Matomo installation at https://matomo.riot.im is currently configured // The Matomo installation at https://matomo.riot.im is currently configured
// with a limit of 10 custom variables. // with a limit of 10 custom variables.
'App Platform': { 'App Platform': {
@ -120,7 +138,7 @@ const customVariables = {
}, },
}; };
function whitelistRedact(whitelist, str) { function whitelistRedact(whitelist: string[], str: string): string {
if (whitelist.includes(str)) return str; if (whitelist.includes(str)) return str;
return '<redacted>'; return '<redacted>';
} }
@ -130,7 +148,7 @@ const CREATION_TS_KEY = "mx_Riot_Analytics_cts";
const VISIT_COUNT_KEY = "mx_Riot_Analytics_vc"; const VISIT_COUNT_KEY = "mx_Riot_Analytics_vc";
const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts"; const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts";
function getUid() { function getUid(): string {
try { try {
let data = localStorage && localStorage.getItem(UID_KEY); let data = localStorage && localStorage.getItem(UID_KEY);
if (!data && localStorage) { if (!data && localStorage) {
@ -145,32 +163,36 @@ function getUid() {
const HEARTBEAT_INTERVAL = 30 * 1000; // seconds const HEARTBEAT_INTERVAL = 30 * 1000; // seconds
class Analytics { export class Analytics {
private baseUrl: URL = null;
private siteId: string = null;
private visitVariables: Record<number, [string, string]> = {}; // {[id: number]: [name: string, value: string]}
private firstPage = true;
private heartbeatIntervalID: number = null;
private readonly creationTs: string;
private readonly lastVisitTs: string;
private readonly visitCount: string;
constructor() { constructor() {
this.baseUrl = null;
this.siteId = null;
this.visitVariables = {};
this.firstPage = true;
this._heartbeatIntervalID = null;
this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY); this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY);
if (!this.creationTs && localStorage) { if (!this.creationTs && localStorage) {
localStorage.setItem(CREATION_TS_KEY, this.creationTs = new Date().getTime()); localStorage.setItem(CREATION_TS_KEY, this.creationTs = String(new Date().getTime()));
} }
this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY); this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY);
this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || 0; this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || "0";
this.visitCount = String(parseInt(this.visitCount, 10) + 1); // increment
if (localStorage) { if (localStorage) {
localStorage.setItem(VISIT_COUNT_KEY, parseInt(this.visitCount, 10) + 1); localStorage.setItem(VISIT_COUNT_KEY, this.visitCount);
} }
} }
get disabled() { public get disabled() {
return !this.baseUrl; return !this.baseUrl;
} }
canEnable() { public canEnable() {
const config = SdkConfig.get(); const config = SdkConfig.get();
return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId; return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId;
} }
@ -179,67 +201,67 @@ class Analytics {
* Enable Analytics if initialized but disabled * Enable Analytics if initialized but disabled
* otherwise try and initalize, no-op if piwik config missing * otherwise try and initalize, no-op if piwik config missing
*/ */
async enable() { public async enable() {
if (!this.disabled) return; if (!this.disabled) return;
if (!this.canEnable()) return; if (!this.canEnable()) return;
const config = SdkConfig.get(); const config = SdkConfig.get();
this.baseUrl = new URL("piwik.php", config.piwik.url); this.baseUrl = new URL("piwik.php", config.piwik.url);
// set constants // set constants
this.baseUrl.searchParams.set("rec", 1); // rec is required for tracking this.baseUrl.searchParams.set("rec", "1"); // rec is required for tracking
this.baseUrl.searchParams.set("idsite", config.piwik.siteId); // rec is required for tracking this.baseUrl.searchParams.set("idsite", config.piwik.siteId); // rec is required for tracking
this.baseUrl.searchParams.set("apiv", 1); // API version to use this.baseUrl.searchParams.set("apiv", "1"); // API version to use
this.baseUrl.searchParams.set("send_image", 0); // we want a 204, not a tiny GIF this.baseUrl.searchParams.set("send_image", "0"); // we want a 204, not a tiny GIF
// set user parameters // set user parameters
this.baseUrl.searchParams.set("_id", getUid()); // uuid this.baseUrl.searchParams.set("_id", getUid()); // uuid
this.baseUrl.searchParams.set("_idts", this.creationTs); // first ts this.baseUrl.searchParams.set("_idts", this.creationTs); // first ts
this.baseUrl.searchParams.set("_idvc", parseInt(this.visitCount, 10)+ 1); // visit count this.baseUrl.searchParams.set("_idvc", this.visitCount); // visit count
if (this.lastVisitTs) { if (this.lastVisitTs) {
this.baseUrl.searchParams.set("_viewts", this.lastVisitTs); // last visit ts this.baseUrl.searchParams.set("_viewts", this.lastVisitTs); // last visit ts
} }
const platform = PlatformPeg.get(); const platform = PlatformPeg.get();
this._setVisitVariable('App Platform', platform.getHumanReadableName()); this.setVisitVariable('App Platform', platform.getHumanReadableName());
try { try {
this._setVisitVariable('App Version', await platform.getAppVersion()); this.setVisitVariable('App Version', await platform.getAppVersion());
} catch (e) { } catch (e) {
this._setVisitVariable('App Version', 'unknown'); this.setVisitVariable('App Version', 'unknown');
} }
this._setVisitVariable('Chosen Language', getCurrentLanguage()); this.setVisitVariable('Chosen Language', getCurrentLanguage());
const hostname = window.location.hostname; const hostname = window.location.hostname;
if (hostname === 'riot.im') { if (hostname === 'riot.im') {
this._setVisitVariable('Instance', window.location.pathname); this.setVisitVariable('Instance', window.location.pathname);
} else if (hostname.endsWith('.element.io')) { } else if (hostname.endsWith('.element.io')) {
this._setVisitVariable('Instance', hostname.replace('.element.io', '')); this.setVisitVariable('Instance', hostname.replace('.element.io', ''));
} }
let installedPWA = "unknown"; let installedPWA = "unknown";
try { try {
// Known to work at least for desktop Chrome // Known to work at least for desktop Chrome
installedPWA = window.matchMedia('(display-mode: standalone)').matches; installedPWA = String(window.matchMedia('(display-mode: standalone)').matches);
} catch (e) { } } catch (e) { }
this._setVisitVariable('Installed PWA', installedPWA); this.setVisitVariable('Installed PWA', installedPWA);
let touchInput = "unknown"; let touchInput = "unknown";
try { try {
// MDN claims broad support across browsers // MDN claims broad support across browsers
touchInput = window.matchMedia('(pointer: coarse)').matches; touchInput = String(window.matchMedia('(pointer: coarse)').matches);
} catch (e) { } } catch (e) { }
this._setVisitVariable('Touch Input', touchInput); this.setVisitVariable('Touch Input', touchInput);
// start heartbeat // start heartbeat
this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL); this.heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL);
} }
/** /**
* Disable Analytics, stop the heartbeat and clear identifiers from localStorage * Disable Analytics, stop the heartbeat and clear identifiers from localStorage
*/ */
disable() { public disable() {
if (this.disabled) return; if (this.disabled) return;
this.trackEvent('Analytics', 'opt-out'); this.trackEvent('Analytics', 'opt-out');
window.clearInterval(this._heartbeatIntervalID); window.clearInterval(this.heartbeatIntervalID);
this.baseUrl = null; this.baseUrl = null;
this.visitVariables = {}; this.visitVariables = {};
localStorage.removeItem(UID_KEY); localStorage.removeItem(UID_KEY);
@ -248,7 +270,7 @@ class Analytics {
localStorage.removeItem(LAST_VISIT_TS_KEY); localStorage.removeItem(LAST_VISIT_TS_KEY);
} }
async _track(data) { private async _track(data: IData) {
if (this.disabled) return; if (this.disabled) return;
const now = new Date(); const now = new Date();
@ -264,13 +286,13 @@ class Analytics {
s: now.getSeconds(), s: now.getSeconds(),
}; };
const url = new URL(this.baseUrl); const url = new URL(this.baseUrl.toString()); // copy
for (const key in params) { for (const key in params) {
url.searchParams.set(key, params[key]); url.searchParams.set(key, params[key]);
} }
try { try {
await window.fetch(url, { await window.fetch(url.toString(), {
method: "GET", method: "GET",
mode: "no-cors", mode: "no-cors",
cache: "no-cache", cache: "no-cache",
@ -281,14 +303,14 @@ class Analytics {
} }
} }
ping() { public ping() {
this._track({ this._track({
ping: 1, ping: "1",
}); });
localStorage.setItem(LAST_VISIT_TS_KEY, new Date().getTime()); // update last visit ts localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts
} }
trackPageChange(generationTimeMs) { public trackPageChange(generationTimeMs?: number) {
if (this.disabled) return; if (this.disabled) return;
if (this.firstPage) { if (this.firstPage) {
// De-duplicate first page // De-duplicate first page
@ -303,11 +325,11 @@ class Analytics {
} }
this._track({ this._track({
gt_ms: generationTimeMs, gt_ms: String(generationTimeMs),
}); });
} }
trackEvent(category, action, name, value) { public trackEvent(category: string, action: string, name?: string, value?: string) {
if (this.disabled) return; if (this.disabled) return;
this._track({ this._track({
e_c: category, e_c: category,
@ -317,12 +339,12 @@ class Analytics {
}); });
} }
_setVisitVariable(key, value) { private setVisitVariable(key: keyof typeof customVariables, value: string) {
if (this.disabled) return; if (this.disabled) return;
this.visitVariables[customVariables[key].id] = [key, value]; this.visitVariables[customVariables[key].id] = [key, value];
} }
setLoggedIn(isGuest, homeserverUrl, identityServerUrl) { public setLoggedIn(isGuest: boolean, homeserverUrl: string) {
if (this.disabled) return; if (this.disabled) return;
const config = SdkConfig.get(); const config = SdkConfig.get();
@ -330,16 +352,16 @@ class Analytics {
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || []; const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In'); this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
} }
setBreadcrumbs(state) { public setBreadcrumbs(state: boolean) {
if (this.disabled) return; if (this.disabled) return;
this._setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled'); this.setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled');
} }
showDetailsModal = () => { public showDetailsModal = () => {
let rows = []; let rows = [];
if (!this.disabled) { if (!this.disabled) {
rows = Object.values(this.visitVariables); rows = Object.values(this.visitVariables);
@ -360,7 +382,7 @@ class Analytics {
'e.g. <CurrentPageURL>', 'e.g. <CurrentPageURL>',
{}, {},
{ {
CurrentPageURL: getRedactedUrl(), CurrentPageURL: getRedactedUrl,
}, },
), ),
}, },
@ -401,7 +423,7 @@ class Analytics {
}; };
} }
if (!global.mxAnalytics) { if (!window.mxAnalytics) {
global.mxAnalytics = new Analytics(); window.mxAnalytics = new Analytics();
} }
export default global.mxAnalytics; export default window.mxAnalytics;

View file

@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
'use strict'; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {User} from "matrix-js-sdk/src/models/user";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
import DMRoomMap from './utils/DMRoomMap'; import DMRoomMap from './utils/DMRoomMap';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
export type ResizeMethod = "crop" | "scale";
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already // Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(member, width, height, resizeMethod) { export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
let url; let url: string;
if (member && member.getAvatarUrl) { if (member && member.getAvatarUrl) {
url = member.getAvatarUrl( url = member.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(), MatrixClientPeg.get().getHomeserverUrl(),
@ -41,7 +46,7 @@ export function avatarUrlForMember(member, width, height, resizeMethod) {
return url; return url;
} }
export function avatarUrlForUser(user, width, height, resizeMethod) { export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
const url = getHttpUriForMxc( const url = getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl, MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
Math.floor(width * window.devicePixelRatio), Math.floor(width * window.devicePixelRatio),
@ -54,14 +59,14 @@ export function avatarUrlForUser(user, width, height, resizeMethod) {
return url; return url;
} }
function isValidHexColor(color) { function isValidHexColor(color: string): boolean {
return typeof color === "string" && return typeof color === "string" &&
(color.length === 7 || color.lengh === 9) && (color.length === 7 || color.length === 9) &&
color.charAt(0) === "#" && color.charAt(0) === "#" &&
!color.substr(1).split("").some(c => isNaN(parseInt(c, 16))); !color.substr(1).split("").some(c => isNaN(parseInt(c, 16)));
} }
function urlForColor(color) { function urlForColor(color: string): string {
const size = 40; const size = 40;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = size; canvas.width = size;
@ -79,9 +84,9 @@ function urlForColor(color) {
// XXX: Ideally we'd clear this cache when the theme changes // XXX: Ideally we'd clear this cache when the theme changes
// but since this function is at global scope, it's a bit // but since this function is at global scope, it's a bit
// hard to install a listener here, even if there were a clear event to listen to // hard to install a listener here, even if there were a clear event to listen to
const colorToDataURLCache = new Map(); const colorToDataURLCache = new Map<string, string>();
export function defaultAvatarUrlForString(s) { export function defaultAvatarUrlForString(s: string): string {
if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake
const defaultColors = ['#0DBD8B', '#368bd6', '#ac3ba8']; const defaultColors = ['#0DBD8B', '#368bd6', '#ac3ba8'];
let total = 0; let total = 0;
@ -113,7 +118,7 @@ export function defaultAvatarUrlForString(s) {
* @param {string} name * @param {string} name
* @return {string} the first letter * @return {string} the first letter
*/ */
export function getInitialLetter(name) { export function getInitialLetter(name: string): string {
if (!name) { if (!name) {
// XXX: We should find out what causes the name to sometimes be falsy. // XXX: We should find out what causes the name to sometimes be falsy.
console.trace("`name` argument to `getInitialLetter` not supplied"); console.trace("`name` argument to `getInitialLetter` not supplied");
@ -146,7 +151,7 @@ export function getInitialLetter(name) {
return firstChar.toUpperCase(); return firstChar.toUpperCase();
} }
export function avatarUrlForRoom(room, width, height, resizeMethod) { export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
if (!room) return null; // null-guard if (!room) return null; // null-guard
const explicitRoomAvatar = room.getAvatarUrl( const explicitRoomAvatar = room.getAvatarUrl(

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import extend from './extend';
import dis from './dispatcher/dispatcher'; import dis from './dispatcher/dispatcher';
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
import {MatrixClient} from "matrix-js-sdk/src/client"; import {MatrixClient} from "matrix-js-sdk/src/client";
@ -497,7 +496,7 @@ export default class ContentMessages {
if (file.type.indexOf('image/') === 0) { if (file.type.indexOf('image/') === 0) {
content.msgtype = 'm.image'; content.msgtype = 'm.image';
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => { infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
extend(content.info, imageInfo); Object.assign(content.info, imageInfo);
resolve(); resolve();
}, (e) => { }, (e) => {
console.error(e); console.error(e);
@ -510,7 +509,7 @@ export default class ContentMessages {
} else if (file.type.indexOf('video/') === 0) { } else if (file.type.indexOf('video/') === 0) {
content.msgtype = 'm.video'; content.msgtype = 'm.video';
infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => { infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => {
extend(content.info, videoInfo); Object.assign(content.info, videoInfo);
resolve(); resolve();
}, (e) => { }, (e) => {
content.msgtype = 'm.file'; content.msgtype = 'm.file';

View file

@ -17,7 +17,7 @@ limitations under the License.
import { _t } from './languageHandler'; import { _t } from './languageHandler';
function getDaysArray() { function getDaysArray(): string[] {
return [ return [
_t('Sun'), _t('Sun'),
_t('Mon'), _t('Mon'),
@ -29,7 +29,7 @@ function getDaysArray() {
]; ];
} }
function getMonthsArray() { function getMonthsArray(): string[] {
return [ return [
_t('Jan'), _t('Jan'),
_t('Feb'), _t('Feb'),
@ -46,11 +46,11 @@ function getMonthsArray() {
]; ];
} }
function pad(n) { function pad(n: number): string {
return (n < 10 ? '0' : '') + n; return (n < 10 ? '0' : '') + n;
} }
function twelveHourTime(date, showSeconds=false) { function twelveHourTime(date: Date, showSeconds = false): string {
let hours = date.getHours() % 12; let hours = date.getHours() % 12;
const minutes = pad(date.getMinutes()); const minutes = pad(date.getMinutes());
const ampm = date.getHours() >= 12 ? _t('PM') : _t('AM'); const ampm = date.getHours() >= 12 ? _t('PM') : _t('AM');
@ -62,7 +62,7 @@ function twelveHourTime(date, showSeconds=false) {
return `${hours}:${minutes}${ampm}`; return `${hours}:${minutes}${ampm}`;
} }
export function formatDate(date, showTwelveHour=false) { export function formatDate(date: Date, showTwelveHour = false): string {
const now = new Date(); const now = new Date();
const days = getDaysArray(); const days = getDaysArray();
const months = getMonthsArray(); const months = getMonthsArray();
@ -86,7 +86,7 @@ export function formatDate(date, showTwelveHour=false) {
return formatFullDate(date, showTwelveHour); return formatFullDate(date, showTwelveHour);
} }
export function formatFullDateNoTime(date) { export function formatFullDateNoTime(date: Date): string {
const days = getDaysArray(); const days = getDaysArray();
const months = getMonthsArray(); const months = getMonthsArray();
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', { return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', {
@ -97,7 +97,7 @@ export function formatFullDateNoTime(date) {
}); });
} }
export function formatFullDate(date, showTwelveHour=false) { export function formatFullDate(date: Date, showTwelveHour = false): string {
const days = getDaysArray(); const days = getDaysArray();
const months = getMonthsArray(); const months = getMonthsArray();
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', { return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
@ -109,14 +109,14 @@ export function formatFullDate(date, showTwelveHour=false) {
}); });
} }
export function formatFullTime(date, showTwelveHour=false) { export function formatFullTime(date: Date, showTwelveHour = false): string {
if (showTwelveHour) { if (showTwelveHour) {
return twelveHourTime(date, true); return twelveHourTime(date, true);
} }
return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()); return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds());
} }
export function formatTime(date, showTwelveHour=false) { export function formatTime(date: Date, showTwelveHour = false): string {
if (showTwelveHour) { if (showTwelveHour) {
return twelveHourTime(date); return twelveHourTime(date);
} }
@ -124,7 +124,7 @@ export function formatTime(date, showTwelveHour=false) {
} }
const MILLIS_IN_DAY = 86400000; const MILLIS_IN_DAY = 86400000;
export function wantsDateSeparator(prevEventDate, nextEventDate) { export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean {
if (!nextEventDate || !prevEventDate) { if (!nextEventDate || !prevEventDate) {
return false; return false;
} }

View file

@ -218,7 +218,7 @@ export const Notifier = {
// calculated value. It is determined based upon whether or not the master rule is enabled // calculated value. It is determined based upon whether or not the master rule is enabled
// and other flags. Setting it here would cause a circular reference. // and other flags. Setting it here would cause a circular reference.
Analytics.trackEvent('Notifier', 'Set Enabled', enable); Analytics.trackEvent('Notifier', 'Set Enabled', String(enable));
// make sure that we persist the current setting audio_enabled setting // make sure that we persist the current setting audio_enabled setting
// before changing anything // before changing anything
@ -287,7 +287,7 @@ export const Notifier = {
setPromptHidden: function(hidden: boolean, persistent = true) { setPromptHidden: function(hidden: boolean, persistent = true) {
this.toolbarHidden = hidden; this.toolbarHidden = hidden;
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden); Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', String(hidden));
hideNotificationsToast(); hideNotificationsToast();

View file

@ -19,30 +19,34 @@ limitations under the License.
import {MatrixClientPeg} from "./MatrixClientPeg"; import {MatrixClientPeg} from "./MatrixClientPeg";
import dis from "./dispatcher/dispatcher"; import dis from "./dispatcher/dispatcher";
import Timer from './utils/Timer'; import Timer from './utils/Timer';
import {ActionPayload} from "./dispatcher/payloads";
// Time in ms after that a user is considered as unavailable/away // Time in ms after that a user is considered as unavailable/away
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
const PRESENCE_STATES = ["online", "offline", "unavailable"];
enum State {
Online = "online",
Offline = "offline",
Unavailable = "unavailable",
}
class Presence { class Presence {
constructor() { private unavailableTimer: Timer = null;
this._activitySignal = null; private dispatcherRef: string = null;
this._unavailableTimer = null; private state: State = null;
this._onAction = this._onAction.bind(this);
this._dispatcherRef = null;
}
/** /**
* Start listening the user activity to evaluate his presence state. * Start listening the user activity to evaluate his presence state.
* Any state change will be sent to the homeserver. * Any state change will be sent to the homeserver.
*/ */
async start() { public async start() {
this._unavailableTimer = new Timer(UNAVAILABLE_TIME_MS); this.unavailableTimer = new Timer(UNAVAILABLE_TIME_MS);
// the user_activity_start action starts the timer // the user_activity_start action starts the timer
this._dispatcherRef = dis.register(this._onAction); this.dispatcherRef = dis.register(this.onAction);
while (this._unavailableTimer) { while (this.unavailableTimer) {
try { try {
await this._unavailableTimer.finished(); await this.unavailableTimer.finished();
this.setState("unavailable"); this.setState(State.Unavailable);
} catch (e) { /* aborted, stop got called */ } } catch (e) { /* aborted, stop got called */ }
} }
} }
@ -50,14 +54,14 @@ class Presence {
/** /**
* Stop tracking user activity * Stop tracking user activity
*/ */
stop() { public stop() {
if (this._dispatcherRef) { if (this.dispatcherRef) {
dis.unregister(this._dispatcherRef); dis.unregister(this.dispatcherRef);
this._dispatcherRef = null; this.dispatcherRef = null;
} }
if (this._unavailableTimer) { if (this.unavailableTimer) {
this._unavailableTimer.abort(); this.unavailableTimer.abort();
this._unavailableTimer = null; this.unavailableTimer = null;
} }
} }
@ -65,14 +69,14 @@ class Presence {
* Get the current presence state. * Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum) * @returns {string} the presence state (see PRESENCE enum)
*/ */
getState() { public getState() {
return this.state; return this.state;
} }
_onAction(payload) { private onAction = (payload: ActionPayload) => {
if (payload.action === 'user_activity') { if (payload.action === 'user_activity') {
this.setState("online"); this.setState(State.Online);
this._unavailableTimer.restart(); this.unavailableTimer.restart();
} }
} }
@ -81,13 +85,11 @@ class Presence {
* If the state has changed, the homeserver will be notified. * If the state has changed, the homeserver will be notified.
* @param {string} newState the new presence state (see PRESENCE enum) * @param {string} newState the new presence state (see PRESENCE enum)
*/ */
async setState(newState) { private async setState(newState: State) {
if (newState === this.state) { if (newState === this.state) {
return; return;
} }
if (PRESENCE_STATES.indexOf(newState) === -1) {
throw new Error("Bad presence state: " + newState);
}
const oldState = this.state; const oldState = this.state;
this.state = newState; this.state = newState;

View file

@ -13,9 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { _t } from './languageHandler'; import { _t } from './languageHandler';
export function levelRoleMap(usersDefault) { export function levelRoleMap(usersDefault: number) {
return { return {
undefined: _t('Default'), undefined: _t('Default'),
0: _t('Restricted'), 0: _t('Restricted'),
@ -25,7 +26,7 @@ export function levelRoleMap(usersDefault) {
}; };
} }
export function textualPowerLevel(level, usersDefault) { export function textualPowerLevel(level: number, usersDefault: number): string {
const LEVEL_ROLE_MAP = levelRoleMap(usersDefault); const LEVEL_ROLE_MAP = levelRoleMap(usersDefault);
if (LEVEL_ROLE_MAP[level]) { if (LEVEL_ROLE_MAP[level]) {
return LEVEL_ROLE_MAP[level]; return LEVEL_ROLE_MAP[level];

View file

@ -38,26 +38,23 @@ const RECENTLY_ACTIVE_THRESHOLD_MS = 2 * 60 * 1000;
* see doc on the userActive* functions for what these mean. * see doc on the userActive* functions for what these mean.
*/ */
export default class UserActivity { export default class UserActivity {
constructor(windowObj, documentObj) { private readonly activeNowTimeout: Timer;
this._window = windowObj; private readonly activeRecentlyTimeout: Timer;
this._document = documentObj; private attachedActiveNowTimers: Timer[] = [];
private attachedActiveRecentlyTimers: Timer[] = [];
private lastScreenX = 0;
private lastScreenY = 0;
this._attachedActiveNowTimers = []; constructor(private readonly window: Window, private readonly document: Document) {
this._attachedActiveRecentlyTimers = []; this.activeNowTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS);
this._activeNowTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS); this.activeRecentlyTimeout = new Timer(RECENTLY_ACTIVE_THRESHOLD_MS);
this._activeRecentlyTimeout = new Timer(RECENTLY_ACTIVE_THRESHOLD_MS);
this._onUserActivity = this._onUserActivity.bind(this);
this._onWindowBlurred = this._onWindowBlurred.bind(this);
this._onPageVisibilityChanged = this._onPageVisibilityChanged.bind(this);
this.lastScreenX = 0;
this.lastScreenY = 0;
} }
static sharedInstance() { static sharedInstance() {
if (global.mxUserActivity === undefined) { if (window.mxUserActivity === undefined) {
global.mxUserActivity = new UserActivity(window, document); window.mxUserActivity = new UserActivity(window, document);
} }
return global.mxUserActivity; return window.mxUserActivity;
} }
/** /**
@ -69,8 +66,8 @@ export default class UserActivity {
* later on when the user does become active. * later on when the user does become active.
* @param {Timer} timer the timer to use * @param {Timer} timer the timer to use
*/ */
timeWhileActiveNow(timer) { public timeWhileActiveNow(timer: Timer) {
this._timeWhile(timer, this._attachedActiveNowTimers); this.timeWhile(timer, this.attachedActiveNowTimers);
if (this.userActiveNow()) { if (this.userActiveNow()) {
timer.start(); timer.start();
} }
@ -85,14 +82,14 @@ export default class UserActivity {
* later on when the user does become active. * later on when the user does become active.
* @param {Timer} timer the timer to use * @param {Timer} timer the timer to use
*/ */
timeWhileActiveRecently(timer) { public timeWhileActiveRecently(timer: Timer) {
this._timeWhile(timer, this._attachedActiveRecentlyTimers); this.timeWhile(timer, this.attachedActiveRecentlyTimers);
if (this.userActiveRecently()) { if (this.userActiveRecently()) {
timer.start(); timer.start();
} }
} }
_timeWhile(timer, attachedTimers) { private timeWhile(timer: Timer, attachedTimers: Timer[]) {
// important this happens first // important this happens first
const index = attachedTimers.indexOf(timer); const index = attachedTimers.indexOf(timer);
if (index === -1) { if (index === -1) {
@ -112,36 +109,36 @@ export default class UserActivity {
/** /**
* Start listening to user activity * Start listening to user activity
*/ */
start() { public start() {
this._document.addEventListener('mousedown', this._onUserActivity); this.document.addEventListener('mousedown', this.onUserActivity);
this._document.addEventListener('mousemove', this._onUserActivity); this.document.addEventListener('mousemove', this.onUserActivity);
this._document.addEventListener('keydown', this._onUserActivity); this.document.addEventListener('keydown', this.onUserActivity);
this._document.addEventListener("visibilitychange", this._onPageVisibilityChanged); this.document.addEventListener("visibilitychange", this.onPageVisibilityChanged);
this._window.addEventListener("blur", this._onWindowBlurred); this.window.addEventListener("blur", this.onWindowBlurred);
this._window.addEventListener("focus", this._onUserActivity); this.window.addEventListener("focus", this.onUserActivity);
// can't use document.scroll here because that's only the document // can't use document.scroll here because that's only the document
// itself being scrolled. Need to use addEventListener's useCapture. // itself being scrolled. Need to use addEventListener's useCapture.
// also this needs to be the wheel event, not scroll, as scroll is // also this needs to be the wheel event, not scroll, as scroll is
// fired when the view scrolls down for a new message. // fired when the view scrolls down for a new message.
this._window.addEventListener('wheel', this._onUserActivity, { this.window.addEventListener('wheel', this.onUserActivity, {
passive: true, capture: true, passive: true,
capture: true,
}); });
} }
/** /**
* Stop tracking user activity * Stop tracking user activity
*/ */
stop() { public stop() {
this._document.removeEventListener('mousedown', this._onUserActivity); this.document.removeEventListener('mousedown', this.onUserActivity);
this._document.removeEventListener('mousemove', this._onUserActivity); this.document.removeEventListener('mousemove', this.onUserActivity);
this._document.removeEventListener('keydown', this._onUserActivity); this.document.removeEventListener('keydown', this.onUserActivity);
this._window.removeEventListener('wheel', this._onUserActivity, { this.window.removeEventListener('wheel', this.onUserActivity, {
passive: true, capture: true, capture: true,
}); });
this.document.removeEventListener("visibilitychange", this.onPageVisibilityChanged);
this._document.removeEventListener("visibilitychange", this._onPageVisibilityChanged); this.window.removeEventListener("blur", this.onWindowBlurred);
this._window.removeEventListener("blur", this._onWindowBlurred); this.window.removeEventListener("focus", this.onUserActivity);
this._window.removeEventListener("focus", this._onUserActivity);
} }
/** /**
@ -151,8 +148,8 @@ export default class UserActivity {
* user's attention at any given moment. * user's attention at any given moment.
* @returns {boolean} true if user is currently 'active' * @returns {boolean} true if user is currently 'active'
*/ */
userActiveNow() { public userActiveNow() {
return this._activeNowTimeout.isRunning(); return this.activeNowTimeout.isRunning();
} }
/** /**
@ -163,27 +160,27 @@ export default class UserActivity {
* (or they may have gone to make tea and left the window focused). * (or they may have gone to make tea and left the window focused).
* @returns {boolean} true if user has been active recently * @returns {boolean} true if user has been active recently
*/ */
userActiveRecently() { public userActiveRecently() {
return this._activeRecentlyTimeout.isRunning(); return this.activeRecentlyTimeout.isRunning();
} }
_onPageVisibilityChanged(e) { private onPageVisibilityChanged = e => {
if (this._document.visibilityState === "hidden") { if (this.document.visibilityState === "hidden") {
this._activeNowTimeout.abort(); this.activeNowTimeout.abort();
this._activeRecentlyTimeout.abort(); this.activeRecentlyTimeout.abort();
} else { } else {
this._onUserActivity(e); this.onUserActivity(e);
} }
} };
_onWindowBlurred() { private onWindowBlurred = () => {
this._activeNowTimeout.abort(); this.activeNowTimeout.abort();
this._activeRecentlyTimeout.abort(); this.activeRecentlyTimeout.abort();
} };
_onUserActivity(event) { private onUserActivity = (event: MouseEvent) => {
// ignore anything if the window isn't focused // ignore anything if the window isn't focused
if (!this._document.hasFocus()) return; if (!this.document.hasFocus()) return;
if (event.screenX && event.type === "mousemove") { if (event.screenX && event.type === "mousemove") {
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) { if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
@ -195,25 +192,25 @@ export default class UserActivity {
} }
dis.dispatch({action: 'user_activity'}); dis.dispatch({action: 'user_activity'});
if (!this._activeNowTimeout.isRunning()) { if (!this.activeNowTimeout.isRunning()) {
this._activeNowTimeout.start(); this.activeNowTimeout.start();
dis.dispatch({action: 'user_activity_start'}); dis.dispatch({action: 'user_activity_start'});
this._runTimersUntilTimeout(this._attachedActiveNowTimers, this._activeNowTimeout); UserActivity.runTimersUntilTimeout(this.attachedActiveNowTimers, this.activeNowTimeout);
} else { } else {
this._activeNowTimeout.restart(); this.activeNowTimeout.restart();
} }
if (!this._activeRecentlyTimeout.isRunning()) { if (!this.activeRecentlyTimeout.isRunning()) {
this._activeRecentlyTimeout.start(); this.activeRecentlyTimeout.start();
this._runTimersUntilTimeout(this._attachedActiveRecentlyTimers, this._activeRecentlyTimeout); UserActivity.runTimersUntilTimeout(this.attachedActiveRecentlyTimers, this.activeRecentlyTimeout);
} else { } else {
this._activeRecentlyTimeout.restart(); this.activeRecentlyTimeout.restart();
} }
} };
async _runTimersUntilTimeout(attachedTimers, timeout) { private static async runTimersUntilTimeout(attachedTimers: Timer[], timeout: Timer) {
attachedTimers.forEach((t) => t.start()); attachedTimers.forEach((t) => t.start());
try { try {
await timeout.finished(); await timeout.finished();

View file

@ -14,19 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Room} from "matrix-js-sdk/src/models/room";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import {MatrixClientPeg} from "./MatrixClientPeg"; import {MatrixClientPeg} from "./MatrixClientPeg";
import { _t } from './languageHandler'; import { _t } from './languageHandler';
export function usersTypingApartFromMeAndIgnored(room) { export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
return usersTyping( return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers()));
room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers()),
);
} }
export function usersTypingApartFromMe(room) { export function usersTypingApartFromMe(room: Room): RoomMember[] {
return usersTyping( return usersTyping(room, [MatrixClientPeg.get().getUserId()]);
room, [MatrixClientPeg.get().credentials.userId],
);
} }
/** /**
@ -34,15 +33,11 @@ export function usersTypingApartFromMe(room) {
* to exclude, return a list of user objects who are typing. * to exclude, return a list of user objects who are typing.
* @param {Room} room: room object to get users from. * @param {Room} room: room object to get users from.
* @param {string[]} exclude: list of user mxids to exclude. * @param {string[]} exclude: list of user mxids to exclude.
* @returns {string[]} list of user objects who are typing. * @returns {RoomMember[]} list of user objects who are typing.
*/ */
export function usersTyping(room, exclude) { export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
const whoIsTyping = []; const whoIsTyping = [];
if (exclude === undefined) {
exclude = [];
}
const memberKeys = Object.keys(room.currentState.members); const memberKeys = Object.keys(room.currentState.members);
for (let i = 0; i < memberKeys.length; ++i) { for (let i = 0; i < memberKeys.length; ++i) {
const userId = memberKeys[i]; const userId = memberKeys[i];
@ -57,20 +52,21 @@ export function usersTyping(room, exclude) {
return whoIsTyping; return whoIsTyping;
} }
export function whoIsTypingString(whoIsTyping, limit) { export function whoIsTypingString(whoIsTyping: RoomMember[], limit: number): string {
let othersCount = 0; let othersCount = 0;
if (whoIsTyping.length > limit) { if (whoIsTyping.length > limit) {
othersCount = whoIsTyping.length - limit + 1; othersCount = whoIsTyping.length - limit + 1;
} }
if (whoIsTyping.length === 0) { if (whoIsTyping.length === 0) {
return ''; return '';
} else if (whoIsTyping.length === 1) { } else if (whoIsTyping.length === 1) {
return _t('%(displayName)s is typing …', {displayName: whoIsTyping[0].name}); return _t('%(displayName)s is typing …', {displayName: whoIsTyping[0].name});
} }
const names = whoIsTyping.map(function(m) {
return m.name; const names = whoIsTyping.map(m => m.name);
});
if (othersCount>=1) { if (othersCount >= 1) {
return _t('%(names)s and %(count)s others are typing …', { return _t('%(names)s and %(count)s others are typing …', {
names: names.slice(0, limit - 1).join(', '), names: names.slice(0, limit - 1).join(', '),
count: othersCount, count: othersCount,

View file

@ -22,6 +22,7 @@ import ImageView from '../elements/ImageView';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import * as Avatar from '../../../Avatar'; import * as Avatar from '../../../Avatar';
import {ResizeMethod} from "../../../Avatar";
interface IProps { interface IProps {
// Room may be left unset here, but if it is, // Room may be left unset here, but if it is,
@ -32,7 +33,7 @@ interface IProps {
oobData?: any; oobData?: any;
width?: number; width?: number;
height?: number; height?: number;
resizeMethod?: string; resizeMethod?: ResizeMethod;
viewAvatarOnClick?: boolean; viewAvatarOnClick?: boolean;
} }

View file

@ -31,7 +31,7 @@ interface IProps {
// The badge to display above the icon // The badge to display above the icon
badge?: React.ReactNode; badge?: React.ReactNode;
// The parameters to track the click event // The parameters to track the click event
analytics: string[]; analytics: Parameters<typeof Analytics.trackEvent>;
// Button name // Button name
name: string; name: string;

View file

@ -76,7 +76,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
}; };
private viewRoom = (room: Room, index: number) => { private viewRoom = (room: Room, index: number) => {
Analytics.trackEvent("Breadcrumbs", "click_node", index); Analytics.trackEvent("Breadcrumbs", "click_node", String(index));
defaultDispatcher.dispatch({action: "view_room", room_id: room.roomId}); defaultDispatcher.dispatch({action: "view_room", room_id: room.roomId});
}; };

View file

@ -16,6 +16,6 @@ limitations under the License.
const EMAIL_ADDRESS_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; const EMAIL_ADDRESS_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
export function looksValid(email) { export function looksValid(email: string): boolean {
return EMAIL_ADDRESS_REGEX.test(email); return EMAIL_ADDRESS_REGEX.test(email);
} }

View file

@ -1,26 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
export default function(dest, src) {
for (const i in src) {
if (src.hasOwnProperty(i)) {
dest[i] = src[i];
}
}
return dest;
}

View file

@ -96,7 +96,7 @@ function safeCounterpartTranslate(text: string, options?: object) {
return translated; return translated;
} }
interface IVariables { export interface IVariables {
count?: number; count?: number;
[key: string]: number | string; [key: string]: number | string;
} }

View file

@ -14,10 +14,20 @@
limitations under the License. limitations under the License.
*/ */
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
function memberEventDiff(ev) { interface IDiff {
const diff = { isMemberEvent: boolean;
isJoin?: boolean;
isPart?: boolean;
isDisplaynameChange?: boolean;
isAvatarChange?: boolean;
}
function memberEventDiff(ev: MatrixEvent): IDiff {
const diff: IDiff = {
isMemberEvent: ev.getType() === 'm.room.member', isMemberEvent: ev.getType() === 'm.room.member',
}; };
@ -37,7 +47,7 @@ function memberEventDiff(ev) {
return diff; return diff;
} }
export default function shouldHideEvent(ev) { export default function shouldHideEvent(ev: MatrixEvent): boolean {
// Wrap getValue() for readability. Calling the SettingsStore can be // Wrap getValue() for readability. Calling the SettingsStore can be
// fairly resource heavy, so the checks below should avoid hitting it // fairly resource heavy, so the checks below should avoid hitting it
// where possible. // where possible.

View file

@ -64,7 +64,7 @@ describe('UserActivity', function() {
it('should not consider user active after activity if no window focus', function() { it('should not consider user active after activity if no window focus', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(false); fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
expect(userActivity.userActiveNow()).toBe(false); expect(userActivity.userActiveNow()).toBe(false);
expect(userActivity.userActiveRecently()).toBe(false); expect(userActivity.userActiveRecently()).toBe(false);
}); });
@ -72,7 +72,7 @@ describe('UserActivity', function() {
it('should consider user active shortly after activity', function() { it('should consider user active shortly after activity', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
expect(userActivity.userActiveNow()).toBe(true); expect(userActivity.userActiveNow()).toBe(true);
expect(userActivity.userActiveRecently()).toBe(true); expect(userActivity.userActiveRecently()).toBe(true);
clock.tick(200); clock.tick(200);
@ -83,7 +83,7 @@ describe('UserActivity', function() {
it('should consider user not active after 10s of no activity', function() { it('should consider user not active after 10s of no activity', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(10000); clock.tick(10000);
expect(userActivity.userActiveNow()).toBe(false); expect(userActivity.userActiveNow()).toBe(false);
}); });
@ -91,7 +91,7 @@ describe('UserActivity', function() {
it('should consider user passive after 10s of no activity', function() { it('should consider user passive after 10s of no activity', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(10000); clock.tick(10000);
expect(userActivity.userActiveRecently()).toBe(true); expect(userActivity.userActiveRecently()).toBe(true);
}); });
@ -99,7 +99,7 @@ describe('UserActivity', function() {
it('should not consider user passive after 10s if window un-focused', function() { it('should not consider user passive after 10s if window un-focused', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(10000); clock.tick(10000);
fakeDocument.hasFocus = jest.fn().mockReturnValue(false); fakeDocument.hasFocus = jest.fn().mockReturnValue(false);
@ -111,7 +111,7 @@ describe('UserActivity', function() {
it('should not consider user passive after 3 mins', function() { it('should not consider user passive after 3 mins', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(3 * 60 * 1000); clock.tick(3 * 60 * 1000);
expect(userActivity.userActiveRecently()).toBe(false); expect(userActivity.userActiveRecently()).toBe(false);
@ -120,11 +120,11 @@ describe('UserActivity', function() {
it('should extend timer on activity', function() { it('should extend timer on activity', function() {
fakeDocument.hasFocus = jest.fn().mockReturnValue(true); fakeDocument.hasFocus = jest.fn().mockReturnValue(true);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(1 * 60 * 1000); clock.tick(1 * 60 * 1000);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(1 * 60 * 1000); clock.tick(1 * 60 * 1000);
userActivity._onUserActivity({}); userActivity.onUserActivity({});
clock.tick(1 * 60 * 1000); clock.tick(1 * 60 * 1000);
expect(userActivity.userActiveRecently()).toBe(true); expect(userActivity.userActiveRecently()).toBe(true);