raiden-ts
Version:
Raiden Light Client Typescript/Javascript SDK
88 lines • 5.21 kB
JavaScript
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import { of } from 'rxjs';
import { catchError, concatMap, distinctUntilChanged, endWith, exhaustMap, filter, first, groupBy, ignoreElements, map, mergeMap, pluck, shareReplay, startWith, switchMap, tap, timeout, withLatestFrom, } from 'rxjs/operators';
import { channelMonitored } from '../../channels/actions';
import { intervalFromConfig } from '../../config';
import { choosePfs$, getPresenceFromService$ } from '../../services/utils';
import { isActionOf } from '../../utils/actions';
import { networkErrors } from '../../utils/error';
import { catchAndLog, completeWith, retryWhile, withMergeFrom } from '../../utils/rx';
import { matrixPresence } from '../actions';
import { stringifyCaps } from '../utils';
/**
* Fetch peer's presence info from services
*
* @param address - Address of interest
* @param pfs$ - Observable of cached best PFS
* @param deps - Epics dependencies
* @returns Observable of user with most recent presence
*/
function searchAddressPresence$(address, pfs$, deps) {
return pfs$.pipe(withLatestFrom(deps.config$), concatMap(([{ url }, { httpTimeout }]) => getPresenceFromService$(address, url, deps).pipe(timeout(httpTimeout),
// this catchAndLog will suppress error and retry next PFS only if error is a networkError,
// otherwise (e.g. address offline) will error early and become matrixPresence.failure
catchAndLog({ onErrors: networkErrors, log: deps.log.debug }, 'Error fetching presence from service', address, url))), first(), catchError((err) => of(matrixPresence.failure(err, { address }))));
}
/**
* Handles MatrixRequestMonitorPresenceAction and emits a MatrixPresenceUpdateAction
* If presence is already known, emits it, else fetch from user profile
* Even if the presence stays the same, we emit a MatrixPresenceUpdateAction, as this may be a
* request being waited by a promise or something like that
* IOW: every request should be followed by a presence update or a failed action, but presence
* updates may happen later without new requests (e.g. when the user goes offline)
*
* @param action$ - Observable of matrixPresence.request actions
* @param state$ - Observable of RaidenStates
* @param deps - RaidenEpicDeps members
* @param deps.matrix$ - MatrixClient async subject
* @param deps.log - Logger instance
* @param deps.config$ - Config observable
* @returns Observable of presence updates or fail action
*/
export function matrixMonitorPresenceEpic(action$, {}, deps) {
const { latest$, config$ } = deps;
const cache = new Map();
const pfs$ = choosePfs$(undefined, deps, true).pipe(shareReplay());
return action$.pipe(tap((action) => {
if (matrixPresence.success.is(action))
cache.set(action.meta.address, action);
else if (matrixPresence.failure.is(action))
cache.delete(action.meta.address);
}), filter(isActionOf(matrixPresence.request)),
// this mergeMap is like withLatestFrom, but waits until matrix$ emits its only value
groupBy((action) => action.meta.address), mergeMap((grouped$) => grouped$.pipe(withLatestFrom(latest$, config$),
// if we're already fetching presence for this address, no need to fetch again
exhaustMap(([action, { rtc }, { pollingInterval }]) => {
const { address } = action.meta;
const cached = cache.get(address);
// we already fetched this peer's presence recently, or there's an RTC channel with them
if (cached && (Date.now() - cached.payload.ts < pollingInterval || address in rtc))
return of(cached);
return searchAddressPresence$(address, pfs$, deps);
}))));
}
/**
* Channel monitoring triggers matrix presence monitoring for partner
*
* @param action$ - Observable of RaidenActions
* @returns Observable of matrixPresence.request actions
*/
export function matrixMonitorChannelPresenceEpic(action$) {
return action$.pipe(filter(channelMonitored.is), map((action) => matrixPresence.request(undefined, { address: action.meta.partner })));
}
/**
* Update our matrix's avatarUrl on config.caps on startup and changes
*
* @param action$ - Observable of RaidenActions
* @param state$ - Observable of RaidenStates
* @param deps - Epics dependencies
* @param deps.matrix$ - MatrixClient async subject
* @param deps.config$ - Config object
* @param deps.init$ - Init$ subject
* @returns Observable which never emits
*/
export function matrixUpdateCapsEpic(action$, {}, { matrix$, config$, init$ }) {
return config$.pipe(completeWith(action$), pluck('caps'), distinctUntilChanged(isEqual), withLatestFrom(init$.pipe(ignoreElements(), startWith(false), endWith(true))), switchMap(([caps, synced]) => matrix$.pipe(withMergeFrom(async (matrix) => matrix.setAvatarUrl(caps && !isEmpty(caps) ? stringifyCaps(caps) : '')), filter(() => synced), withMergeFrom(async ([matrix]) => matrix.setPresence({ presence: 'offline', status_msg: '' })), withMergeFrom(async ([[matrix]]) => matrix.setPresence({ presence: 'online', status_msg: Date.now().toString() })), retryWhile(intervalFromConfig(config$)))), ignoreElements());
}
//# sourceMappingURL=presence.js.map