rxjs/operators#pairwise TypeScript Examples
The following examples show how to use
rxjs/operators#pairwise.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: app.module.ts From rubic-app with GNU General Public License v3.0 | 6 votes |
/**
* Defines scroll strategy, when page url is changed.
* Doesn't scroll if only query parameters are changed.
*/
private setScrollStrategy(): void {
this.router.events
.pipe(
filter((e: Event): e is Scroll => e instanceof Scroll),
pairwise()
)
.subscribe(([prevEvent, event]) => {
if (event.position) {
// backward navigation
this.viewportScroller.scrollToPosition(event.position);
} else if (event.anchor) {
// anchor navigation
this.viewportScroller.scrollToAnchor(event.anchor);
} else if (
prevEvent.routerEvent.urlAfterRedirects.split('?')[0] !==
event.routerEvent.urlAfterRedirects.split('?')[0]
) {
// forward navigation
this.viewportScroller.scrollToPosition([0, 0]);
}
});
}
Example #2
Source File: swaps.service.ts From rubic-app with GNU General Public License v3.0 | 6 votes |
private subscribeOnForm(): void {
this.swapFormService.inputValueChanges
.pipe(startWith(null, this.swapFormService.inputValue), pairwise())
.subscribe(([prevForm, curForm]) => {
this.setSwapProviderType(curForm);
if (
(!TokensService.areTokensEqual(prevForm?.fromToken, curForm.fromToken) &&
curForm.fromToken) ||
(!TokensService.areTokensEqual(prevForm?.toToken, curForm.toToken) && curForm.toToken)
) {
this.updateTokensPrices(curForm);
}
if (
!TokensService.areTokensEqual(prevForm?.fromToken, curForm.fromToken) &&
curForm.fromToken
) {
this.updateTokenBalance(curForm.fromToken);
}
});
}
Example #3
Source File: router-outlet.element.ts From scion-microfrontend-platform with Eclipse Public License 2.0 | 6 votes |
private installOutletUrlListener(): void {
this._outletName$
.pipe(
switchMap(outlet => outletNavigate$(outlet).pipe(startWith(null! as Navigation))), // start with a `null` navigation in case no navigation took place yet
tap(navigation => this._empty$.next(!navigation || navigation.url === 'about:blank')),
distinctUntilChanged((url1, url2) => url1 === url2, navigation => navigation?.url),
pairwise(),
takeUntil(this._disconnect$),
)
.subscribe(([prevNavigation, currNavigation]: [Navigation, Navigation]) => runSafe(() => {
// Emit a page deactivate event, unless not having a previous navigation
prevNavigation && this.dispatchEvent(new CustomEvent('deactivate', {detail: prevNavigation.url}));
// Change the outlet URL
Beans.get(RouterOutletUrlAssigner).assign(this._iframe, currNavigation || {url: 'about:blank', pushStateToSessionHistoryStack: false}, prevNavigation);
// Emit a page activate event, unless not having a current navigation
currNavigation && this.dispatchEvent(new CustomEvent('activate', {detail: currNavigation.url}));
}));
}
Example #4
Source File: network.service.ts From fyle-mobile-app with MIT License | 6 votes |
getConnectionStatus() {
return this.isConnected$.pipe(
startWith(true),
pairwise(),
switchMap(([previousConnectionStatus, currentConnectionStatus]) => {
if (previousConnectionStatus === false && currentConnectionStatus === true) {
return concat(
of(ConnectionMessageStatus.onlineMessageShown),
of(ConnectionMessageStatus.onlineMessageHidden).pipe(delay(3000))
);
} else if (previousConnectionStatus === true && currentConnectionStatus === true) {
return of(ConnectionMessageStatus.onlineMessageHidden);
} else {
return of(ConnectionMessageStatus.disconnected);
}
})
);
}
Example #5
Source File: camera.service.ts From ionic-pwa-example-moment with MIT License | 6 votes |
function revokePreviousImageUrl() {
return (source$: Observable<string>) =>
source$.pipe(
startWith(undefined),
pairwise(),
tap(([previous]) => {
if (previous) URL.revokeObjectURL(previous);
}),
concatMapTo(source$)
);
}
Example #6
Source File: RestApi.ts From majsoul-api with MIT License | 6 votes |
private getSessions(contest: store.Contest<ObjectId>): Observable<Session> {
return concat(
defer(() => from(
this.mongoStore.sessionsCollection.find(
{ contestId: contest._id },
{ sort: { scheduledTime: 1 } }
).toArray()
)).pipe(
mergeAll(),
),
of<store.Session<ObjectId>>(null)
).pipe(
pairwise(),
map(([session, nextSession]) =>
defer(() => from(this.getSessionSummary(contest, session, nextSession)))
.pipe(
map(totals => {
return { ...session, totals, aggregateTotals: totals };
})
)
),
mergeAll(),
);
}
Example #7
Source File: gantt-dom.service.ts From ngx-gantt with MIT License | 6 votes |
/**
* @returns An observable that will emit outside the Angular zone. Note, consumers should re-enter the Angular zone
* to run the change detection if needed.
*/
getViewerScroll(options?: AddEventListenerOptions): Observable<ScrollEvent> {
return new Observable<ScrollEvent>((subscriber) =>
this.ngZone.runOutsideAngular(() =>
fromEvent(this.mainContainer, 'scroll', options)
.pipe(
map(() => this.mainContainer.scrollLeft),
pairwise(),
map(([previous, current]) => {
const event: ScrollEvent = {
target: this.mainContainer,
direction: ScrollDirection.NONE
};
if (current - previous < 0) {
if (this.mainContainer.scrollLeft < scrollThreshold && this.mainContainer.scrollLeft > 0) {
event.direction = ScrollDirection.LEFT;
}
}
if (current - previous > 0) {
if (
this.mainContainer.scrollWidth - this.mainContainer.clientWidth - this.mainContainer.scrollLeft <
scrollThreshold
) {
event.direction = ScrollDirection.RIGHT;
}
}
return event;
})
)
.subscribe(subscriber)
)
);
}
Example #8
Source File: speed-chart.component.ts From RcloneNg with MIT License | 5 votes |
ngOnInit() {
this.browserSettingService
.partialBrowserSetting$('rng.speedChart.windowSize')
.subscribe(([v, err]) => (this.treadhold = v));
const statsOut = this.stats$.getOutput();
statsOut.subscribe(node => {
if (node[1].length !== 0) return;
const time = moment();
let speed = 0;
let avg = 0;
if (node[0]['core-stats'].transferring) {
node[0]['core-stats'].transferring.forEach(x => {
if (x.speed) speed += x.speed;
if (x.speedAvg) avg += x.speedAvg;
});
}
const speedData = this.lineChartData[0].data as ChartPoint[];
const avgData = this.lineChartData[1].data as ChartPoint[];
const threadhold = time.clone().subtract(this.treadhold, 'seconds');
while (speedData.length !== 0 && speedData[0].x < threadhold) {
speedData.shift();
avgData.shift();
}
speedData.push({ x: time, y: speed });
avgData.push({ x: time, y: avg });
this.chart.update();
});
statsOut.pipe(pairwise()).subscribe(([pre, cur]) => {
if (pre[1].length !== 0 || cur[1].length !== 0) return;
let preSpeed = 0;
let curSpeed = 0;
if (pre[0]['core-stats'].transferring) {
pre[0]['core-stats'].transferring.forEach(x => {
if (x.speed) preSpeed += x.speed;
});
}
if (cur[0]['core-stats'].transferring) {
cur[0]['core-stats'].transferring.forEach(x => {
if (x.speed) curSpeed += x.speed;
});
}
this.speedDiff = Math.round(curSpeed - preSpeed);
});
}
Example #9
Source File: ngx-hide-on-scroll.directive.ts From Elastos.Essentials.App with MIT License | 5 votes |
init() {
if (isPlatformServer(this.platformId)) {
return;
}
this.reset();
let elementToListenScrollEvent;
let scrollingElement: HTMLElement;
if (!this.scrollingElementSelector) {
elementToListenScrollEvent = window;
scrollingElement = this.getDefaultScrollingElement();
} else {
scrollingElement = document.querySelector(this.scrollingElementSelector) as HTMLElement;
if (!scrollingElement) {
console.warn(`NgxHideOnScroll: @Input() scrollingElementSelector\nElement with selector: "${this.scrollingElementSelector}" not found.`);
return;
}
elementToListenScrollEvent = scrollingElement;
}
/* elementToListenScrollEvent.addEventListener("scroll", e => {
console.log("MANUAL ON SCROLL", e)
}) */
/* elementToListenScrollEvent.addEventListener("ionScroll", e => {
console.log("MANUAL ON ION-SCROLL", e)
})
*/
const scroll$ = fromEvent<ScrollCustomEvent>(elementToListenScrollEvent, 'ionScroll').pipe(
takeUntil(this.unsubscribeNotifier),
throttleTime(50), // only emit every 50 ms
map(event => event.detail.scrollTop), // get vertical scroll position
pairwise(), // look at this and the last emitted element
// compare this and the last element to figure out scrolling direction
map(([y1, y2]): ScrollDirection => (y2 < y1 ? ScrollDirection.Up : ScrollDirection.Down)),
distinctUntilChanged(), // only emit when scrolling direction changed
share(), // share a single subscription to the underlying sequence in case of multiple subscribers
);
const scrollUp$ = scroll$.pipe(
filter(direction => direction === ScrollDirection.Up)
);
const scrollDown$ = scroll$.pipe(
filter(direction => direction === ScrollDirection.Down)
);
let scrollUpAction: () => void;
let scrollDownAction: () => void;
if (this.hideOnScroll === 'Up') {
scrollUpAction = () => this.hideElement(scrollingElement);
scrollDownAction = () => this.showElement(scrollingElement);
} else {
scrollUpAction = () => this.showElement(scrollingElement);
scrollDownAction = () => this.hideElement(scrollingElement);
}
scrollUp$.subscribe(() => {
scrollUpAction()
});
scrollDown$.subscribe(() => {
scrollDownAction()
});
}
Example #10
Source File: select.ts From ble with Apache License 2.0 | 5 votes |
entityMove: Epic = (action$, { store }) => {
return action$.pipe (
ofType('entityPointerDown'),
filter(() => store.editor.mode === EditorMode.select),
// middle click is panning only
filter(({ ev }) => !(ev.data.pointerType === 'mouse' && ev.data.button === 1)),
// it's important to use global and not original event
// because TouchEvents don't have clientX
pluck('ev', 'data', 'global'),
// we copy the relevant data because react pools events
map(({ x, y }) => ({
x: x + store.editor.renderZone.x,
y: y + store.editor.renderZone.y,
})),
tap(() => {
store.undoManager.startGroup();
}),
switchMap(({ x, y }) => fromEvent<PointerEvent>(document, 'pointermove').pipe(
map(({ clientX, clientY }) => new Vector(clientX, clientY)),
startWith(new Vector(x, y)),
pairwise(),
map(([prev, curr]) => curr.clone().sub(prev)),
filter((vec) => vec.len2() !== 0),
map((vec) => vec.scale(1/store.editor.scale)),
scan((acc, delta) => {
const totalDelta = acc.clone().add(delta);
const displacement = snapBoxToGrid(
new Box(
store.editor.selectionAsAabb.pos.clone().add(totalDelta),
store.editor.selectionAsAabb.w,
store.editor.selectionAsAabb.h,
),
store.editor.gridCellSize,
);
store.editor.selection.forEach((entity: IEntity) => {
entity.params.move(totalDelta.x + displacement.x, totalDelta.y + displacement.y);
});
return displacement.reverse();
}, new Vector(0, 0)),
takeUntil(fromEvent(document, 'pointerup').pipe(
tap(() => {
store.undoManager.stopGroup();
}),
)),
)),
ignoreElements(),
);
}
Example #11
Source File: CollateralAuctionsTask.ts From guardian with Apache License 2.0 | 5 votes |
async start(guardian: AcalaGuardian) {
const { apiRx } = await guardian.isReady();
const { account, currencyId } = this.arguments;
let currencies: CurrencyId[] = [];
const whitelist = apiRx.consts.cdpEngine.collateralCurrencyIds;
// make sure provided currency id is whitelisted
if (currencyId !== 'all') {
currencies = castArray(currencyId).map((x) => apiRx.createType('CurrencyId', x));
currencies.forEach((id) => {
if (!whitelist.find((x) => x.eq(id))) throw Error('Collateral currency id not allowed!');
});
} else {
currencies = whitelist;
}
const includesAccount = includesArgument<string>(account);
const includesCurrency = includesArgument<CurrencyId>(currencies);
const upcomingAuctions$ = apiRx.query.auction.auctionsIndex().pipe(
pairwise(),
filter(([, next]) => !next.isZero()),
switchMap(([prev, next]) => range(prev.toNumber(), next.toNumber())),
distinctUntilChanged(),
mergeMap((auctionId) => {
return combineLatest([
of(auctionId),
apiRx.query.auctionManager.collateralAuctions(auctionId),
apiRx.query.auction.auctions(auctionId)
]);
})
);
return apiRx.query.auctionManager.collateralAuctions.entries().pipe(
mergeMap((entry) => entry),
mergeMap((entry) => {
const [storageKey, maybecollateralAuction] = entry;
const [auctionId] = storageKey.args;
return combineLatest([of(auctionId), of(maybecollateralAuction), apiRx.query.auction.auctions(auctionId)]);
}),
concatWith(upcomingAuctions$),
filter(([, maybecollateralAuction, maybeAuction]) => {
if (maybecollateralAuction.isNone) return false;
if (maybeAuction.isNone) return false;
const { refundRecipient, currencyId } = maybecollateralAuction.unwrap();
if (!includesAccount(refundRecipient.toString())) return false;
if (!includesCurrency(currencyId)) return false;
return true;
}),
map(([auctionId, maybecollateralAuction, maybeAuction]) => {
const collateralAuction = maybecollateralAuction.unwrap();
const auction = maybeAuction.unwrap();
const [lastBidder, lastBid] = auction.bid.isSome ? auction.bid.unwrap() : [];
return {
account: collateralAuction.refundRecipient.toString(),
currencyId: collateralAuction.currencyId.asToken.toString(),
auctionId: Number(auctionId.toString()),
initialAmount: collateralAuction.initialAmount.toString(),
amount: collateralAuction.amount.toString(),
target: collateralAuction.target.toString(),
startTime: Number(collateralAuction.startTime.toString()),
endTime: auction.end.isSome ? Number(auction.end.toString()) : undefined,
lastBidder: lastBidder && lastBidder.toString(),
lastBid: lastBid && lastBid.toString()
};
}),
drr()
);
}
Example #12
Source File: LiquidityPoolTask.ts From guardian with Apache License 2.0 | 5 votes |
getSyntheticPools = (apiRx: ApiRx) => (poolId: number | number[] | 'all') => {
const upcomingPools$ = apiRx.query.baseLiquidityPoolsForSynthetic.nextPoolId().pipe(
pairwise(),
filter(([, next]) => !next.isZero()),
switchMap(([prev, next]) => range(prev.toNumber(), next.toNumber())),
distinctUntilChanged(),
mergeMap((poolId) =>
combineLatest([
of(poolId.toString()),
apiRx.query.baseLiquidityPoolsForSynthetic.pools(poolId).pipe(
filter((x) => x.isSome),
map((x) => x.unwrap())
)
])
)
);
if (poolId === 'all') {
return apiRx.query.baseLiquidityPoolsForSynthetic.pools.entries().pipe(
mergeMap((x) => x),
filter(([, value]) => value.isSome),
mergeMap(
([
{
args: [poolId]
},
pool
]) => combineLatest([of(poolId.toString()), of(pool.unwrap())])
),
concatWith(upcomingPools$)
);
} else {
return of(castArray(poolId)).pipe(
mergeMap((x) => x),
switchMap((poolId) =>
combineLatest([of(poolId.toString()), apiRx.query.baseLiquidityPoolsForSynthetic.pools(poolId)])
),
filter(([, pool]) => pool.isSome),
mergeMap(([poolId, value]) => combineLatest([of(poolId), of(value.unwrap())]))
);
}
}
Example #13
Source File: RestApi.ts From majsoul-api with MIT License | 5 votes |
private async getLeaguePhaseData({
contest,
transitions,
phases
}: PhaseInfo): Promise<LeaguePhase<ObjectID>[]> {
const sessions = (await this.getSessions(contest).pipe(toArray()).toPromise())
.sort((a, b) => a.scheduledTime - b.scheduledTime);
return from(phases.concat(null)).pipe(
pairwise(),
mergeScan((completePhase, [phase, nextPhase]) => {
const transition = transitions[phase.index];
const phaseSessions = sessions.filter(
session =>
session.scheduledTime >= phase.startTime
&& (nextPhase == null || session.scheduledTime < nextPhase.startTime)
);
const startingTotals = {
...completePhase.sessions[completePhase.sessions.length - 1]?.aggregateTotals ?? {}
};
const rankedTeams = Object.entries(startingTotals)
.map(([team, score]) => ({ team, score }))
.sort((a, b) => b.score - a.score);
const allowedTeams = transition.teams?.top
? rankedTeams.slice(0, transition.teams.top)
.reduce((total, next) => (total[next.team] = true, total), {} as Record<string, true>)
: null;
for (const team of Object.keys(startingTotals)) {
if (allowedTeams && !(team in allowedTeams)) {
delete startingTotals[team];
continue;
}
if (transition.score?.half) {
startingTotals[team] = Math.floor(startingTotals[team] / 2);
} else if (transition.score?.nil) {
startingTotals[team] = 0;
}
}
return of({
...phase,
sessions: phaseSessions.reduce((total, next) => {
const aggregateTotals = { ...(total[total.length - 1]?.aggregateTotals ?? startingTotals) };
const filteredTotals = Object.entries(next.totals)
.filter(([key]) => !allowedTeams || key in allowedTeams)
.reduce((total, [key, value]) => (total[key] = value, total), {} as Record<string, number>);
for (const team in filteredTotals) {
if (aggregateTotals[team] == null) {
aggregateTotals[team] = 0;
}
aggregateTotals[team] += filteredTotals[team];
}
total.push({
...next,
totals: filteredTotals,
aggregateTotals,
})
return total;
}, [] as Session<ObjectID>[]),
aggregateTotals: startingTotals,
} as LeaguePhase<ObjectID>);
}, {
sessions: [{
aggregateTotals: (contest.teams ?? []).reduce(
(total, next) => (total[next._id.toHexString()] = 0, total),
{} as Record<string, number>
)
}]
} as LeaguePhase<ObjectID>, 1),
toArray(),
).toPromise();
}
Example #14
Source File: dashboard.component.ts From RcloneNg with MIT License | 4 votes |
ngOnInit(): void {
this.resp.getResponsiveSize.subscribe(data => {
this.isSmallerThanSmSize = data === 'xs';
});
const outer = this;
this.visable = true;
this.stats$ = new (class extends CoreStatsFlow {
public prerequest$ = combineLatest([
outer.cmdService.rst$.getOutput(),
outer.cmdService.listCmd$.verify(this.cmd),
]).pipe(
takeWhile(() => outer.visable),
map(([, node]): CombErr<CoreStatsFlowInNode> => node)
);
})();
this.stats$.deploy();
this.mem$ = new (class extends CoreMemstatsFlow {
protected cacheSupport = false;
public prerequest$ = combineLatest([
outer.cmdService.rst$.getOutput(),
outer.cmdService.listCmd$.verify(this.cmd),
]).pipe(
takeWhile(() => outer.visable),
map(x => x[1])
);
})();
this.mem$.deploy();
this.mem$.getOutput().subscribe(x => {
if (x[1].length !== 0) return;
this.memVals = { ...x[0]['mem-stats'] };
for (const key in this.memVals) {
if (this.memVals.hasOwnProperty(key)) {
this.memVals[key] = FormatBytes(this.memVals[key], 3);
}
}
});
this.mem$
.getOutput()
.pipe(pairwise())
.subscribe(([pre, cur]) => {
if (pre[1].length !== 0 || cur[1].length !== 0) return;
for (const key in pre[0]['mem-stats']) {
if (this.memVals.hasOwnProperty(key)) {
this.memDiff[key] = cur[0]['mem-stats'][key] - pre[0]['mem-stats'][key];
}
}
});
this.memTrigger.next(1);
this.ver$ = new (class extends CoreVersionFlow {
public prerequest$: Observable<CombErr<IRcloneServer>> = outer.cmdService.listCmd$.verify(
this.cmd
);
})();
this.ver$.deploy();
this.ver$.getOutput().subscribe(x => {
if (x[1].length !== 0) return;
this.verVals = x[0];
});
this.bwlimit$ = new (class extends CoreBwlimitFlow {
public prerequest$ = combineLatest([
outer.cmdService.listCmd$.verify(this.cmd),
outer.bwlimitTrigger.pipe(map(input => (input === '' ? {} : { rate: input }))),
]).pipe(
map(
([userNode, params]): CombErr<CoreBwlimitFlowInNode> => [
{ ...userNode[0], ...params },
userNode[1],
]
)
);
})();
this.bwlimit$.deploy();
this.bwlimit$.getOutput().subscribe(x => {
if (x[1].length !== 0) return;
this.limitation = this.limitationServer = x[0].bandwidth.rate;
});
this.bwlimitTrigger.next(''); // query bandwidth
}
Example #15
Source File: connector.ts From majsoul-api with MIT License | 4 votes |
async function main() {
const secrets = getSecrets();
const mongoStore = new store.Store();
try {
await mongoStore.init(secrets.mongo?.username ?? "root", secrets.mongo?.password ?? "example");
} catch (error) {
console.log("failed to connect to mongo db: ", error);
process.exit(1);
}
const userAgent = await getOrGenerateUserAgent(mongoStore);
const [config] = await mongoStore.configCollection.find().toArray();
const expireDeadline = Date.now() + 60 * 1000;
const existingCookies = (config.loginCookies ?? []).filter(cookie => !cookie.expires || cookie.expires > expireDeadline);
const {passport: dynamicPassport, loginCookies} = (await getPassport(
{
userId: secrets.majsoul.uid,
accessToken: secrets.majsoul.accessToken,
userAgent,
existingCookies,
}
)) ?? {};
await mongoStore.configCollection.updateOne(
{
_id: config._id,
},
{
$set: {
loginCookies
},
}
);
if (dynamicPassport) {
await mongoStore.configCollection.updateOne(
{
_id: config._id,
},
{
$set: {
passportToken: dynamicPassport.accessToken
},
}
);
}
const passportToken = dynamicPassport?.accessToken ?? config.passportToken ?? secrets.majsoul.passportToken;
if (!passportToken) {
console.log("failed to aquire passport");
process.exit(1);
}
const passport: Passport = {
accessToken: passportToken,
uid: secrets.majsoul.uid,
};
const apiResources = await majsoul.Api.retrieveApiResources();
console.log(`Using api version ${apiResources.pbVersion}`);
const adminApi = new AdminApi();
const api = new majsoul.Api(apiResources);
api.notifications.subscribe(n => console.log(n));
await api.init();
// console.log(api.majsoulCodec.decodeMessage(Buffer.from("", "hex")));
await api.logIn(passport);
api.errors$.subscribe((error => {
console.log("error detected with api connection: ", error);
process.exit(1);
}));
//spreadsheet.addGameDetails(await api.getGame(decodePaipuId("jijpnt-q3r346x6-y108-64fk-hbbn-lkptsjjyoszx_a925250810_2").split('_')[0]));
// api.getGame(
// // Codec.decodePaipuId("")
// // ""
// ).then(game => {
// parseGameRecordResponse(game);
// // console.log(util.inspect(game.head, false, null));
// // console.log(util.inspect(parseGameRecordResponse(game), false, null));
// });
const googleAuth = new google.auth.OAuth2(
secrets.google.clientId,
secrets.google.clientSecret,
);
const googleTokenValid$ = process.env.NODE_ENV === "production"
? concat(
defer(() => googleAuth.getAccessToken()).pipe(
map(response => response.token),
catchError(() => of(null))
),
fromEvent<Credentials>(googleAuth, "tokens").pipe(
map((tokens) => tokens?.access_token),
)
).pipe(
distinctUntilChanged(),
map(token => token != null),
shareReplay(1),
)
: of(false);
googleTokenValid$.subscribe(tokenIsValid => {
console.log(`google token is ${tokenIsValid ? "" : "in"}valid`);
})
// oauth token
merge(
mongoStore.ConfigChanges.pipe(
filter(change => change.operationType === "update"
&& change.updateDescription.updatedFields.googleRefreshToken !== undefined
),
map((updateEvent: ChangeEventUpdate<store.Config<ObjectId>>) => updateEvent.updateDescription.updatedFields.googleRefreshToken)
),
defer(
() => from(
mongoStore.configCollection.find().toArray()
).pipe(
mergeAll(),
map(config => config.googleRefreshToken)
)
)
).subscribe(refresh_token => {
if (googleAuth.credentials.refresh_token === refresh_token || refresh_token == null) {
console.log(`refresh token not valid in database`);
return;
}
googleAuth.setCredentials({
refresh_token
});
googleAuth.getRequestHeaders();
});
// player search
merge(
mongoStore.PlayerChanges.pipe(
filter(change => change.operationType === "insert"
&& change.fullDocument.majsoulFriendlyId != null
),
map((insertEvent: ChangeEventCR<store.Player<ObjectId>>) => insertEvent.fullDocument)
),
defer(() => from(
mongoStore.playersCollection.find({
majsoulFriendlyId: {
$exists: true
}
}).toArray()
).pipe(mergeAll()))
).subscribe(player => {
api.findPlayerByFriendlyId(player.majsoulFriendlyId).then(async (apiPlayer) => {
if (apiPlayer == null) {
mongoStore.playersCollection.deleteOne({
_id: player._id
});
return;
}
const update = await mongoStore.playersCollection.findOneAndUpdate(
{
majsoulId: apiPlayer.majsoulId
},
{
$set: {
...apiPlayer
},
}
);
if (update.value) {
mongoStore.playersCollection.deleteOne({
_id: player._id
});
return;
}
mongoStore.playersCollection.findOneAndUpdate(
{
_id: player._id
},
{
$set: {
...apiPlayer
},
$unset: {
majsoulFriendlyId: true
}
}
);
})
})
// custom game id search
merge(
mongoStore.GameChanges.pipe(
filter(change => change.operationType === "insert"
&& change.fullDocument.contestMajsoulId == null
&& change.fullDocument.majsoulId != null
),
map((insertEvent: ChangeEventCR<store.GameResult<ObjectId>>) => insertEvent.fullDocument)
),
defer(() => from(
mongoStore.gamesCollection.find({
notFoundOnMajsoul: {
$exists: false
},
contestMajsoulId: {
$exists: false
}
}).toArray()
).pipe(mergeAll()))
).subscribe(game => {
console.log(`Custom game id added ${game.majsoulId}`);
recordGame(
game.contestId,
game.majsoulId,
mongoStore,
api
);
});
createContestIds$(mongoStore).subscribe((contestId) => {
const tracker = new ContestTracker(contestId, mongoStore, api);
concat(
of(null),
tracker.MajsoulId$.pipe(distinctUntilChanged())
).pipe(pairwise())
.subscribe(([previous, next]) => {
if (next == null && previous != null) {
mongoStore.gamesCollection.updateMany(
{
contestMajsoulId: previous
},
{
$unset: {
contestId: true,
}
}
);
return;
}
mongoStore.gamesCollection.updateMany(
{
contestMajsoulId: next
},
{
$set: {
contestId: contestId,
}
}
);
});
tracker.AdminPlayerFetchRequested$
.pipe(filter(fetchRequested => fetchRequested == true))
.subscribe(async () => {
const contest = await mongoStore.contestCollection.findOneAndUpdate(
{ _id: contestId },
{ $unset: { adminPlayerFetchRequested: true } },
{ projection: {
adminPlayerFetchRequested: true,
majsoulId: true
}}
);
console.log(`fetchRequested for contest #${contestId} #${contest.value.majsoulId}` );
await adminApi.reconnect();
try {
await adminApi.logIn(passport);
await adminApi.manageContest(contest.value.majsoulId);
const { players, error } = await adminApi.fetchContestPlayers();
if (error) {
console.log(error);
return;
}
const existingPlayers = await mongoStore.playersCollection.find({
majsoulId: {
$in: players.map(player => player.account_id)
}
}).toArray();
const newPlayers = players.filter(player => existingPlayers.find(existingPlayer => existingPlayer.majsoulId === player.account_id) == null);
if (!newPlayers.length) {
console.log("No new players to add found");
return;
}
console.log(`Inserting ${newPlayers.length} players`);
await mongoStore.playersCollection.insertMany(
newPlayers.map(player => ({
nickname: player.nickname,
majsoulId: player.account_id,
}))
);
} finally {
adminApi.disconnect();
}
});
tracker.UpdateRequest$.subscribe(async (majsoulFriendlyId) => {
const majsoulContest = await api.findContestByContestId(majsoulFriendlyId);
if (majsoulContest == null) {
mongoStore.contestCollection.findOneAndUpdate(
{ _id: contestId },
{ $set: { notFoundOnMajsoul: true } },
);
console.log(`contest ${majsoulFriendlyId} not found on majsoul`);
return;
}
console.log(`updating contest ${majsoulFriendlyId}`);
mongoStore.contestCollection.findOneAndUpdate(
{ _id: contestId },
{ $set: { ...majsoulContest } },
);
console.log(`updating contest ${majsoulFriendlyId} games`);
for (const gameId of await api.getContestGamesIds(majsoulContest.majsoulId)) {
await recordGame(contestId, gameId.majsoulId, mongoStore, api);
}
});
tracker.LiveGames$.subscribe(gameId => {
recordGame(contestId, gameId, mongoStore, api);
});
const spreadsheet$ = combineLatest([
tracker.SpreadsheetId$,
googleTokenValid$,
]).pipe(
filter(([spreadsheetId, tokenIsValid]) => tokenIsValid && spreadsheetId != null),
share(),
);
spreadsheet$.subscribe(async ([spreadsheetId]) => {
const spreadsheet = new Spreadsheet(spreadsheetId, googleAuth);
try {
await spreadsheet.init();
} catch (error) {
console.log(`Spreadsheet #${spreadsheetId} failed to initialise.`, error);
return;
}
console.log(`Tracking [${spreadsheetId}]`);
tracker.RecordedGames$.pipe(
takeUntil(spreadsheet$),
).subscribe(game => {
spreadsheet.addGame(game);
spreadsheet.addGameDetails(game);
})
tracker.Teams$.pipe(
takeUntil(spreadsheet$),
).subscribe(async (teams) => {
const players = await mongoStore.playersCollection.find({
_id: {
$in: teams.map(team => team.players).flat().map(team => team._id)
}
}).toArray();
spreadsheet.updateTeams(
teams,
players.reduce((total, next) => (total[next._id.toHexString()] = next, total), {})
);
});
})
});
}