mirror of
https://github.com/mempool/mempool.git
synced 2026-08-13 12:33:11 +02:00
Merge pull request #6520 from mempool/mononaut/partner-codes
Some checks failed
Backend Integration Tests with MariaDB / Backend Integration Tests - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Cache assets for builds (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Validate generated backend Docker JSON (push) Has been cancelled
Supply Chain Audit / Backend install-script audit (push) Has been cancelled
Supply Chain Audit / Frontend install-script audit (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for liquid (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for mempool (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for testnet4 (push) Has been cancelled
Some checks failed
Backend Integration Tests with MariaDB / Backend Integration Tests - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Backend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Cache assets for builds (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Validate generated backend Docker JSON (push) Has been cancelled
Supply Chain Audit / Backend install-script audit (push) Has been cancelled
Supply Chain Audit / Frontend install-script audit (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (dev) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / Frontend (prod) - node 24.13.0 (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for liquid (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for mempool (push) Has been cancelled
CI Pipeline for the Backend and Frontend / E2E tests for testnet4 (push) Has been cancelled
refactor partnerCode handling
This commit is contained in:
commit
ffb913da09
4 changed files with 127 additions and 34 deletions
|
|
@ -10,9 +10,9 @@ import { Transaction } from '@interfaces/electrs.interface';
|
|||
import { MiningStats } from '@app/services/mining.service';
|
||||
import { IAuth, AuthServiceMempool } from '@app/services/auth.service';
|
||||
import { EnterpriseService } from '@app/services/enterprise.service';
|
||||
import { PartnerCodeService } from '@app/services/partner-code.service';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { isDevMode } from '@angular/core';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
|
||||
export type PaymentMethod = 'balance' | 'bitcoin' | 'cashapp' | 'applePay' | 'googlePay' | 'cardOnFile';
|
||||
|
|
@ -146,7 +146,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
private cd: ChangeDetectorRef,
|
||||
private authService: AuthServiceMempool,
|
||||
private enterpriseService: EnterpriseService,
|
||||
private storageService: StorageService,
|
||||
private partnerCodeService: PartnerCodeService,
|
||||
private themeService: ThemeService,
|
||||
) {
|
||||
this.isProdDomain = this.stateService.isProdDomain;
|
||||
|
|
@ -562,8 +562,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.partnerCode
|
||||
).subscribe({
|
||||
next: (response) => {
|
||||
this.storageService.removeItem('partnerCode'); // Consume localStorage partnerCode
|
||||
this.partnerCode = undefined;
|
||||
this.partnerCodeService.clearStoredPartnerCode();
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
|
|
@ -687,8 +686,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.partnerCode
|
||||
).subscribe({
|
||||
next: (response) => {
|
||||
this.storageService.removeItem('partnerCode'); // Consume localStorage partnerCode
|
||||
this.partnerCode = undefined;
|
||||
this.partnerCodeService.clearStoredPartnerCode();
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
|
|
@ -792,8 +790,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.partnerCode
|
||||
).subscribe({
|
||||
next: (response) => {
|
||||
this.storageService.removeItem('partnerCode'); // Consume localStorage partnerCode
|
||||
this.partnerCode = undefined;
|
||||
this.partnerCodeService.clearStoredPartnerCode();
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
|
|
@ -883,8 +880,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
this.partnerCode
|
||||
).subscribe({
|
||||
next: (response) => {
|
||||
this.storageService.removeItem('partnerCode'); // Consume localStorage partnerCode
|
||||
this.partnerCode = undefined;
|
||||
this.partnerCodeService.clearStoredPartnerCode();
|
||||
this.accelerationResponse = response;
|
||||
this.processing = false;
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
|
|
@ -966,8 +962,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
bitcoinPaymentCompleted(): void {
|
||||
this.storageService.removeItem('partnerCode'); // Consume localStorage partnerCode
|
||||
this.partnerCode = undefined;
|
||||
this.partnerCodeService.clearStoredPartnerCode();
|
||||
this.apiService.logAccelerationRequest$(this.tx.txid).subscribe();
|
||||
this.audioService.playSound('ascend-chime-cartoon');
|
||||
this.estimateSubscription.unsubscribe();
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ import { BlockExtended, CpfpInfo, RbfTree, MempoolPosition, DifficultyAdjustment
|
|||
import { PriceService } from '@app/services/price.service';
|
||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||
import { EnterpriseService } from '@app/services/enterprise.service';
|
||||
import { PartnerCodeService } from '@app/services/partner-code.service';
|
||||
import { ZONE_SERVICE } from '@app/injection-tokens';
|
||||
import { TrackerStage } from '@components/tracker/tracker-bar.component';
|
||||
import { MiningService, MiningStats } from '@app/services/mining.service';
|
||||
import { ETA, EtaService } from '@app/services/eta.service';
|
||||
import { getTransactionFlags, getUnacceleratedFeeRate } from '@app/shared/transaction.utils';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
|
||||
|
||||
interface Pool {
|
||||
|
|
@ -84,6 +84,8 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||
blocksSubscription: Subscription;
|
||||
miningSubscription: Subscription;
|
||||
currencyChangeSubscription: Subscription;
|
||||
urlFragmentSubscription: Subscription;
|
||||
partnerCodeSubscription: Subscription;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
replaced: boolean = false;
|
||||
latestReplacement: string;
|
||||
|
|
@ -144,25 +146,20 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||
private seoService: SeoService,
|
||||
private priceService: PriceService,
|
||||
private enterpriseService: EnterpriseService,
|
||||
private partnerCodeService: PartnerCodeService,
|
||||
private miningService: MiningService,
|
||||
private router: Router,
|
||||
private cd: ChangeDetectorRef,
|
||||
private zone: NgZone,
|
||||
private storageService: StorageService,
|
||||
@Inject(ZONE_SERVICE) private zoneService: any,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.onResize();
|
||||
|
||||
// Accelerator partner code in fragment
|
||||
const fragmentsParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
this.partnerCode = fragmentsParams.get('partnerCode') ?? this.storageService.getValue('partnerCode') ?? undefined;
|
||||
if (this.partnerCode) {
|
||||
this.storageService.setValue('partnerCode', this.partnerCode);
|
||||
// Cleanup partnerCode from url after storing it in localStorage to avoid re-use upon page reload
|
||||
window.location.hash = window.location.hash.replace(`&partnerCode=${this.partnerCode}`, '').replace(`#partnerCode=${this.partnerCode}`, '');
|
||||
}
|
||||
this.setupPartnerCode();
|
||||
this.urlFragmentSubscription = this.route.fragment.subscribe((fragment) => {
|
||||
this.consumePartnerCodeFragment(fragment);
|
||||
});
|
||||
|
||||
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||
|
||||
|
|
@ -869,6 +866,36 @@ export class TrackerComponent implements OnInit, OnDestroy {
|
|||
this.miningSubscription?.unsubscribe();
|
||||
this.currencyChangeSubscription?.unsubscribe();
|
||||
this.enterpriseInfo$?.unsubscribe();
|
||||
this.urlFragmentSubscription?.unsubscribe();
|
||||
this.leaveTransaction();
|
||||
this.partnerCodeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
setupPartnerCode(): void {
|
||||
this.partnerCodeSubscription?.unsubscribe();
|
||||
this.partnerCodeSubscription = this.partnerCodeService.partnerCode$.subscribe((partnerCode) => {
|
||||
this.partnerCode = partnerCode;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
// extract the partnerCode from the fragment string, pass to the partnerCodeService, and then remove from the URL
|
||||
private consumePartnerCodeFragment(fragment: string | null): void {
|
||||
const fragmentParams = new URLSearchParams(fragment || '');
|
||||
if (!fragmentParams.has('partnerCode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const partnerCode = fragmentParams.get('partnerCode');
|
||||
if (partnerCode) {
|
||||
this.partnerCodeService.setFragmentPartnerCode(partnerCode);
|
||||
}
|
||||
fragmentParams.delete('partnerCode');
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
fragment: fragmentParams.toString() || null,
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import {
|
|||
startWith,
|
||||
repeat,
|
||||
take,
|
||||
debounceTime,
|
||||
distinctUntilChanged
|
||||
} from 'rxjs/operators';
|
||||
import { Transaction } from '@interfaces/electrs.interface';
|
||||
|
|
@ -37,6 +36,7 @@ import { PriceService } from '@app/services/price.service';
|
|||
import { isFeatureActive } from '@app/bitcoin.utils';
|
||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||
import { EnterpriseService } from '@app/services/enterprise.service';
|
||||
import { PartnerCodeService } from '@app/services/partner-code.service';
|
||||
import { ZONE_SERVICE } from '@app/injection-tokens';
|
||||
import { MiningService, MiningStats } from '@app/services/mining.service';
|
||||
import { ETA, EtaService } from '@app/services/eta.service';
|
||||
|
|
@ -104,6 +104,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
auditSubscription: Subscription;
|
||||
txConfirmedSubscription: Subscription;
|
||||
currencyChangeSubscription: Subscription;
|
||||
partnerCodeSubscription: Subscription;
|
||||
fragmentParams: URLSearchParams;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
replaced: boolean = false;
|
||||
|
|
@ -215,6 +216,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private priceService: PriceService,
|
||||
private storageService: StorageService,
|
||||
private enterpriseService: EnterpriseService,
|
||||
private partnerCodeService: PartnerCodeService,
|
||||
private miningService: MiningService,
|
||||
private etaService: EtaService,
|
||||
private cd: ChangeDetectorRef,
|
||||
|
|
@ -222,6 +224,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.setupPartnerCode();
|
||||
|
||||
this.enterpriseService.page();
|
||||
this.isDetailsOpen = this.route.snapshot.queryParams['showDetails'] === 'true';
|
||||
this.cpfpMode = this.route.snapshot.queryParams['cpfp'] === 'true';
|
||||
|
|
@ -229,15 +233,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.forceAccelerationSummary = !!urlParams.get('cash_request_id');
|
||||
|
||||
// Accelerator partner code
|
||||
const fragmentsParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
this.partnerCode = fragmentsParams.get('partnerCode') ?? this.storageService.getValue('partnerCode') ?? undefined;
|
||||
if (this.partnerCode) {
|
||||
this.storageService.setValue('partnerCode', this.partnerCode);
|
||||
// Cleanup partnerCode from url after storing it in localStorage to avoid re-use upon page reload
|
||||
window.location.hash = window.location.hash.replace(`&partnerCode=${this.partnerCode}`, '').replace(`#partnerCode=${this.partnerCode}`, '');
|
||||
}
|
||||
|
||||
this.hideAccelerationSummary = this.stateService.isMempoolSpaceBuild ? this.storageService.getValue('hide-accelerator-pref') == 'true' : true;
|
||||
|
||||
if (!this.stateService.isLiquid()) {
|
||||
|
|
@ -1145,7 +1140,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (anchor) {
|
||||
params.set(anchor, '');
|
||||
}
|
||||
return Array.from(params.entries()).map(([key, value]) => `${key}=${value}`).join('&') || null;
|
||||
return Array.from(params.entries()).map(([key, value]) => value ? `${key}=${value}` : key).join('&') || null;
|
||||
}
|
||||
|
||||
toggleGraph() {
|
||||
|
|
@ -1187,6 +1182,22 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
updateFragmentParams(fragment: string | null): void {
|
||||
this.fragmentParams = new URLSearchParams(fragment || '');
|
||||
// remove any partnerCode param from the URL and pass it to the partnerCodeService
|
||||
if (this.fragmentParams.has('partnerCode')) {
|
||||
const partnerCode = this.fragmentParams.get('partnerCode');
|
||||
if (partnerCode) {
|
||||
this.partnerCodeService.setFragmentPartnerCode(partnerCode);
|
||||
}
|
||||
this.fragmentParams.delete('partnerCode');
|
||||
// update the URL with the new fragment string in a way Angular Router understands
|
||||
const anchor = Array.from(this.fragmentParams.entries()).find(([, value]) => value === '')?.[0] || null;
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
fragment: this.formatFragment(this.fragmentParams, anchor),
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
const vin = parseInt(this.fragmentParams.get('vin'), 10);
|
||||
const vout = parseInt(this.fragmentParams.get('vout'), 10);
|
||||
const inputIndex = (!isNaN(vin) && vin >= 0) ? vin : null;
|
||||
|
|
@ -1298,5 +1309,13 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.txConfirmedSubscription?.unsubscribe();
|
||||
this.currencyChangeSubscription?.unsubscribe();
|
||||
this.leaveTransaction();
|
||||
this.partnerCodeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
setupPartnerCode(): void {
|
||||
this.partnerCodeSubscription?.unsubscribe();
|
||||
this.partnerCodeSubscription = this.partnerCodeService.partnerCode$.subscribe((partnerCode) => {
|
||||
this.partnerCode = partnerCode;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
frontend/src/app/services/partner-code.service.ts
Normal file
52
frontend/src/app/services/partner-code.service.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, shareReplay } from 'rxjs/operators';
|
||||
import { EnterpriseService } from '@app/services/enterprise.service';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PartnerCodeService {
|
||||
private readonly storedPartnerCode$: BehaviorSubject<string | undefined>;
|
||||
|
||||
readonly partnerCode$: Observable<string | undefined>;
|
||||
|
||||
constructor(
|
||||
private enterpriseService: EnterpriseService,
|
||||
private storageService: StorageService,
|
||||
) {
|
||||
// holds the most recent unused one-off code
|
||||
const storedPartnerCode = this.storageService.getValue('partnerCode');
|
||||
const isEnterprise = !!this.enterpriseService.getSubdomain();
|
||||
this.storedPartnerCode$ = new BehaviorSubject<string | undefined>(storedPartnerCode || undefined);
|
||||
|
||||
const enterprisePartnerCode$ = this.enterpriseService.info$.pipe(
|
||||
filter((info: object | null) => !isEnterprise || info !== null),
|
||||
map((info: object | null) => (info as { name?: string } | null)?.name || undefined),
|
||||
);
|
||||
|
||||
// enterprise partner code takes precedence, and remains applicable for all requests on this instance
|
||||
// one-off code applies to the first successful request after being set, iff no enterprise code is present
|
||||
this.partnerCode$ = combineLatest([
|
||||
enterprisePartnerCode$,
|
||||
this.storedPartnerCode$,
|
||||
]).pipe(
|
||||
map(([enterpriseCode, storedCode]) => enterpriseCode || storedCode),
|
||||
distinctUntilChanged(),
|
||||
shareReplay(1),
|
||||
);
|
||||
}
|
||||
|
||||
// set a one-off partner code from a URL fragment
|
||||
setFragmentPartnerCode(partnerCode: string): void {
|
||||
this.storageService.setValue('partnerCode', partnerCode);
|
||||
this.storedPartnerCode$.next(partnerCode);
|
||||
}
|
||||
|
||||
// clear any one-off code after successful use
|
||||
clearStoredPartnerCode(): void {
|
||||
this.storageService.removeItem('partnerCode');
|
||||
this.storedPartnerCode$.next(undefined);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue