Skip to content

Commit 733ebce

Browse files
authored
Merge pull request #371 from csfloat/feature/reversal-status
Display Trade Reversal Status on Steam Profile
2 parents 85bbb8a + b9263df commit 733ebce

9 files changed

Lines changed: 211 additions & 0 deletions

File tree

src/environment.dev.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export const environment = {
55
ws: 'wss://notary.csfloat.com/proxy',
66
loggingLevel: 'Error',
77
},
8+
reverse_watch_base_api_url: 'http://localhost:3434/api',
89
};

src/environment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export const environment = {
55
ws: 'wss://notary.csfloat.com/proxy',
66
loggingLevel: 'Warn',
77
},
8+
reverse_watch_base_api_url: 'https://reverse.watch/api',
89
};
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {RequestType} from './types';
2+
import {SimpleHandler} from './main';
3+
import {environment} from '../../../environment';
4+
5+
export interface FetchReversalStatusRequest {
6+
steam_id64: string;
7+
}
8+
9+
export interface FetchReversalStatusResponse {
10+
steam_id: string;
11+
has_reversed: boolean;
12+
last_reversal_timestamp?: number;
13+
}
14+
15+
export interface FetchReversalStatusError {
16+
code: string;
17+
message: string;
18+
details?: string;
19+
}
20+
21+
export const FetchReversalStatus = new SimpleHandler<FetchReversalStatusRequest, FetchReversalStatusResponse>(
22+
RequestType.FETCH_REVERSAL_STATUS,
23+
async (req) => {
24+
const resp = await fetch(`${environment.reverse_watch_base_api_url}/v1/users/${req.steam_id64}`);
25+
const data = (await resp.json()) as FetchReversalStatusResponse | FetchReversalStatusError;
26+
if (!resp.ok) {
27+
throw Error((data as FetchReversalStatusError).message ?? 'unknown error');
28+
}
29+
return data as FetchReversalStatusResponse;
30+
}
31+
);

src/lib/bridge/handlers/handlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {NotaryProve} from './notary_prove';
3737
import {FetchNotaryMeta} from './fetch_notary_meta';
3838
import {FetchNotaryToken} from './fetch_notary_token';
3939
import {FetchSteamPoweredInventory} from './fetch_steam_inventory';
40+
import {FetchReversalStatus} from './fetch_reversal_status';
4041

4142
export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
4243
[RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage,
@@ -76,4 +77,5 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
7677
[RequestType.FETCH_NOTARY_META]: FetchNotaryMeta,
7778
[RequestType.FETCH_NOTARY_TOKEN]: FetchNotaryToken,
7879
[RequestType.FETCH_STEAM_POWERED_INVENTORY]: FetchSteamPoweredInventory,
80+
[RequestType.FETCH_REVERSAL_STATUS]: FetchReversalStatus,
7981
};

src/lib/bridge/handlers/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ export enum RequestType {
3636
FETCH_NOTARY_META = 34,
3737
FETCH_NOTARY_TOKEN = 35,
3838
FETCH_STEAM_POWERED_INVENTORY = 36,
39+
FETCH_REVERSAL_STATUS = 37,
3940
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import {CustomElement, InjectAfter, InjectionMode} from '../injectors';
2+
import {FloatElement} from '../custom';
3+
import {css, html} from 'lit';
4+
import {state} from 'lit/decorators.js';
5+
import {FetchReversalStatusResponse} from '../../bridge/handlers/fetch_reversal_status';
6+
import {gReversalFetcher} from '../../services/reversal_fetcher';
7+
import {defined} from '../../utils/checkers';
8+
9+
@CustomElement()
10+
@InjectAfter(
11+
'.profile_in_game.persona + .profile_ban_status, .profile_in_game.persona:not(:has(+ .profile_ban_status))',
12+
InjectionMode.ONCE
13+
)
14+
export class ReversalStatus extends FloatElement {
15+
@state()
16+
reversalStatus: FetchReversalStatusResponse | undefined = undefined;
17+
18+
static styles = [
19+
...FloatElement.styles,
20+
css`
21+
.container {
22+
display: flex;
23+
align-items: center;
24+
gap: 6px;
25+
color: #de6667;
26+
margin-bottom: 10px;
27+
28+
.warning {
29+
display: inline;
30+
31+
.info-link-container {
32+
color: #828282;
33+
34+
.info-link {
35+
text-decoration: none;
36+
color: #ebebeb;
37+
38+
&:hover {
39+
color: #66c0f4;
40+
}
41+
}
42+
}
43+
44+
.powered-by-container {
45+
font-size: 12px;
46+
color: #828282;
47+
48+
.powered-by-link {
49+
text-decoration: none;
50+
color: #ebebeb;
51+
52+
&:hover {
53+
color: #66c0f4;
54+
}
55+
}
56+
}
57+
}
58+
}
59+
`,
60+
];
61+
62+
get show(): boolean {
63+
return !!this.reversalStatus?.has_reversed;
64+
}
65+
66+
get daysSinceLastReversal(): number | null {
67+
if (!this.reversalStatus?.last_reversal_timestamp) {
68+
return null;
69+
}
70+
71+
const now = Date.now();
72+
const timeSince = now - this.reversalStatus.last_reversal_timestamp;
73+
return Math.floor(timeSince / (24 * 60 * 60 * 1000));
74+
}
75+
76+
getSteamId(): string | undefined {
77+
if (defined(typeof g_rgProfileData) && g_rgProfileData) {
78+
return g_rgProfileData.steamid;
79+
}
80+
81+
const match = window.location.pathname.match(/^\/profiles\/(\d+)/);
82+
if (match) {
83+
return match[1];
84+
}
85+
}
86+
87+
async connectedCallback() {
88+
super.connectedCallback();
89+
90+
try {
91+
const steamId = this.getSteamId();
92+
if (!steamId) {
93+
console.error('failed to get steam id');
94+
return;
95+
}
96+
97+
this.reversalStatus = await gReversalFetcher.fetch({steam_id64: steamId});
98+
} catch (e) {
99+
console.error('failed to fetch reversal status', e);
100+
}
101+
}
102+
103+
protected render() {
104+
if (!this.show) {
105+
return html``;
106+
}
107+
108+
const daysSince = this.daysSinceLastReversal ?? 0;
109+
const message = `${daysSince} day(s) since last trade reversal`;
110+
return html`
111+
<div class="container">
112+
<div class="warning">
113+
${message}
114+
<span class="info-link-container">
115+
|
116+
<a
117+
class="info-link"
118+
href="https://help.steampowered.com/en/faqs/view/365F-4BEE-2AE2-7BDD"
119+
target="_blank"
120+
rel="noreferrer"
121+
>
122+
Info
123+
</a>
124+
</span>
125+
<span class="powered-by-container"
126+
>(powered by <a class="powered-by-link" href="https://reverse.watch">reverse.watch</a>)</span
127+
>
128+
</div>
129+
</div>
130+
`;
131+
}
132+
}

src/lib/page_scripts/profile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {init} from './utils';
22
import '../components/profile/comment_warning';
3+
import '../components/profile/reversal_status';
34

45
init('src/lib/page_scripts/profile.js', main);
56

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {ClientSend} from '../bridge/client';
2+
import {
3+
FetchReversalStatus,
4+
FetchReversalStatusRequest,
5+
FetchReversalStatusResponse,
6+
} from '../bridge/handlers/fetch_reversal_status';
7+
import {GenericJob, TTLCachedQueue} from '../utils/queue';
8+
9+
class ReversalFetcher extends TTLCachedQueue<FetchReversalStatusRequest, FetchReversalStatusResponse> {
10+
constructor(maxConcurrency: number, ttlMs: number) {
11+
super(maxConcurrency, ttlMs);
12+
}
13+
14+
fetch(req: FetchReversalStatusRequest): Promise<FetchReversalStatusResponse> {
15+
return this.add(new GenericJob(req));
16+
}
17+
18+
protected async process(req: FetchReversalStatusRequest): Promise<FetchReversalStatusResponse> {
19+
try {
20+
return await ClientSend(FetchReversalStatus, req);
21+
} catch (e) {
22+
console.error('failed to fetch reversal status', e);
23+
// Stub out to prevent future calls
24+
return {
25+
steam_id: '',
26+
has_reversed: false,
27+
last_reversal_timestamp: undefined,
28+
} as FetchReversalStatusResponse;
29+
}
30+
}
31+
}
32+
33+
export const gReversalFetcher = new ReversalFetcher(1, 30 * 60 * 1000 /* 30 minutes */);

src/lib/types/steam.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,21 @@ export type SteamAssets = {
250250
};
251251
};
252252

253+
// g_rgProfileData
254+
export interface ProfileData {
255+
url: string;
256+
steamid: string;
257+
personaname: string;
258+
summary?: string;
259+
}
260+
253261
// Declares globals available in the Steam Page Context
254262
declare global {
255263
const $J: typeof $;
256264
const g_rgListingInfo: {[listingId: string]: ListingData};
257265
const g_rgWalletInfo: WalletInfo | undefined; // Not populated when user is signed-out
258266
const g_rgAssets: SteamAssets;
267+
const g_rgProfileData: ProfileData | undefined; // Only populated on Steam profile pages
259268
const g_ActiveInventory: CAppwideInventory | CInventory | undefined; // Only populated on Steam inventory pages
260269
const g_steamID: string;
261270
const g_oSearchResults: CAjaxPagingControls;

0 commit comments

Comments
 (0)