Skip to content

Commit

Permalink
Merge pull request #252 from csfloat/feature/improved-method-connecti…
Browse files Browse the repository at this point in the history
…vity

Implements Handlers for Augmented Trading
  • Loading branch information
Step7750 authored Sep 18, 2024
2 parents 1005b60 + 4622328 commit e0de1e6
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 26 deletions.
17 changes: 13 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "CSFloat Market Checker",
"short_name": "CSFloat",
"version": "4.3.1",
"version": "5.0.0",
"description": "Shows the float value, paint seed, and more of Counter-Strike (CS:GO & CS2) items on the Steam Market or Inventories",
"icons": {
"16": "icons/16.png",
Expand Down Expand Up @@ -43,7 +43,7 @@
"service_worker": "src/background.js",
"type": "module"
},
"permissions": ["storage", "scripting", "alarms"],
"permissions": ["storage", "scripting", "alarms", "declarativeNetRequestWithHostAccess"],
"host_permissions": [
"*://*.steamcommunity.com/market/listings/730/*",
"*://*.steamcommunity.com/id/*/inventory*",
Expand All @@ -52,12 +52,21 @@
],
"optional_host_permissions": ["*://*.steampowered.com/*"],
"externally_connectable": {
"matches": ["*://*.steamcommunity.com/*"]
"matches": ["*://*.steamcommunity.com/*", "*://*.csfloat.com/*"]
},
"web_accessible_resources": [
{
"resources": ["src/version.txt"],
"matches": ["https://csfloat.com/*"]
}
]
],
"declarative_net_request": {
"rule_resources": [
{
"id": "steamcommunity_ruleset",
"enabled": true,
"path": "src/steamcommunity_ruleset.json"
}
]
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "csfloat-extension",
"version": "4.3.1",
"version": "5.0.0",
"description": "Dedicated API for fetching the float value, paint seed, and more of CS:GO items on the Steam Market or Inventories",
"main": ".eslintrc",
"directories": {
Expand Down
10 changes: 8 additions & 2 deletions src/lib/alarms/access_token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export interface AccessToken {
updated_at: number;
}

export async function getAccessToken(): Promise<AccessToken> {
export async function getAccessToken(expectedSteamID?: string): Promise<AccessToken> {
// Do we have a fresh local copy?
const tokenData = await gStore.getWithStorage<AccessToken>(chrome.storage.local, StorageKey.ACCESS_TOKEN);
if (tokenData?.token && tokenData.updated_at > Date.now() - 30 * 60 * 1000) {
// Token refreshed within the last 30 min, we can re-use
return tokenData;
if (!expectedSteamID || expectedSteamID === tokenData?.steam_id) {
return tokenData;
}
}

// Need to fetch a new one
Expand All @@ -30,6 +32,10 @@ export async function getAccessToken(): Promise<AccessToken> {
const token = webAPITokenMatch[1];
const steamID = extractSteamID(body);

if (steamID && expectedSteamID && steamID !== expectedSteamID) {
throw new Error('user is not logged into the expected steam account');
}

try {
await saveAccessToken(token, steamID);
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/alarms/csfloat_trade_pings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {StorageKey} from '../storage/keys';

export const PING_CSFLOAT_TRADE_STATUS_ALARM_NAME = 'ping_csfloat_trade_status_alarm';

export async function pingTradeStatus() {
export async function pingTradeStatus(expectedSteamID?: string) {
await gStore.setWithStorage(chrome.storage.local, StorageKey.LAST_TRADE_PING_ATTEMPT, Date.now());

const hasPermissions = await HasPermissions.handleRequest(
Expand Down Expand Up @@ -38,7 +38,7 @@ export async function pingTradeStatus() {
let access: AccessToken | null = null;

try {
access = await getAccessToken();
access = await getAccessToken(expectedSteamID);
} catch (e) {
console.error('failed to fetch access token', e);
}
Expand Down
32 changes: 32 additions & 0 deletions src/lib/bridge/handlers/cancel_trade_offer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';

export interface CancelTradeOfferRequest {
trade_offer_id: string;
session_id: string;
}

export interface CancelTradeOfferResponse {}

export const CancelTradeOffer = new SimpleHandler<CancelTradeOfferRequest, CancelTradeOfferResponse>(
RequestType.CANCEL_TRADE_OFFER,
async (req) => {
const formData = {
sessionid: req.session_id,
};

const resp = await fetch(`https://steamcommunity.com/tradeoffer/${req.trade_offer_id}/cancel`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: new URLSearchParams(formData as any).toString(),
});

if (!resp.ok) {
throw new Error(`failed to cancel offer: ${resp.status}`);
}

return {} as CancelTradeOfferResponse;
}
);
100 changes: 100 additions & 0 deletions src/lib/bridge/handlers/create_trade_offer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';

export interface CreateTradeOfferRequest {
toSteamID64: string;
tradeToken: string;
message: string;
assetIDsToGive: string[];
assetIDsToReceive: string[];
sessionID: string;
forceEnglish?: boolean;
}

interface CreateOfferSteamResponse {
email_domain?: string;
strError?: string;
needs_email_confirmation?: boolean;
needs_mobile_confirmation?: boolean;
tradeofferid?: string;
}

export interface CreateTradeOfferResponse {
status: number;
json?: CreateOfferSteamResponse;
text?: string;
}

export const CreateTradeOffer = new SimpleHandler<CreateTradeOfferRequest, CreateTradeOfferResponse>(
RequestType.CREATE_TRADE_OFFER,
async (req) => {
function itemMapper(assetID: string) {
return {
appid: 730,
contextid: 2,
amount: 1,
assetid: assetID,
};
}

const offerData = {
newversion: true,
version: req.assetIDsToGive.length + req.assetIDsToReceive.length + 1,
me: {
assets: req.assetIDsToGive.map(itemMapper),
currency: [],
ready: false,
},
them: {
assets: req.assetIDsToReceive.map(itemMapper),
currency: [],
ready: false,
},
};

const params = {
trade_offer_access_token: req.tradeToken,
};

const formData = {
sessionid: req.sessionID,
serverid: 1,
partner: req.toSteamID64,
tradeoffermessage: req.message || 'CSFloat Trade Offer',
json_tradeoffer: JSON.stringify(offerData),
captcha: '',
trade_offer_create_params: JSON.stringify(params),
};

const url = req.forceEnglish
? 'https://steamcommunity.com/tradeoffer/new/send?l=english'
: 'https://steamcommunity.com/tradeoffer/new/send';

const resp = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
// Referer is actually set in the declarative net static rules since
// it is a "protected" header. Setting it here does nothing in major browsers.
// Without it, Steam rejects the request as part of their anti-cross origin block.
},
body: new URLSearchParams(formData as any).toString(),
});

const res: CreateTradeOfferResponse = {
status: resp.status,
};

const text = await resp.text();
res.text = text;

try {
const data = JSON.parse(res.text);
res.json = data;
} catch (e: any) {
console.error(`failed to parse json from Steam create offer: ${e.toString()}`);
}

return res;
}
);
44 changes: 44 additions & 0 deletions src/lib/bridge/handlers/fetch_own_inventory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {rgDescription, rgInventoryAsset} from '../../types/steam';

export interface FetchOwnInventoryRequest {
expected_steam_id: string;
app_id: number;
context_id: number;
}

export interface InventoryResponse {
more?: boolean;
more_start?: boolean;
Error?: string;
success: boolean;
rgDescriptions: Map<string, rgDescription>;
rgInventory: Map<string, rgInventoryAsset>;
}

interface FetchOwnInventoryResponse {
inventory: InventoryResponse;
}

export const FetchOwnInventory = new SimpleHandler<FetchOwnInventoryRequest, FetchOwnInventoryResponse>(
RequestType.FETCH_OWN_INVENTORY,
async (req) => {
// Will error out if expected_steam_id != logged in user
const resp = await fetch(
`https://steamcommunity.com/profiles/${req.expected_steam_id}/inventory/json/${req.app_id}/${req.context_id}/?trading=1`,
{
credentials: 'include',
}
);
if (!resp.ok) {
throw new Error(`Invalid response code: ${resp.status}`);
}

const inventory = (await resp.json()) as InventoryResponse;

return {
inventory,
};
}
);
38 changes: 38 additions & 0 deletions src/lib/bridge/handlers/fetch_steam_user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';

interface FetchSteamUserRequest {}

interface FetchSteamUserResponse {
isLoggedIn: boolean;
steamID?: string;
sessionID?: string;
}

export const FetchSteamUser = new SimpleHandler<FetchSteamUserRequest, FetchSteamUserResponse>(
RequestType.FETCH_STEAM_USER,
async (req) => {
const resp = await fetch('https://steamcommunity.com');
if (!resp.ok) {
throw new Error('non-ok response for steamcommunity.com');
}

const res: FetchSteamUserResponse = {
isLoggedIn: false,
};

const text = await resp.text();
const steamIDMatch = text.match(/g_steamID = "(\d+)"/);
if (steamIDMatch) {
res.isLoggedIn = true;
res.steamID = steamIDMatch[1];
}

const sessionIDMatch = text.match(/g_sessionID = "([0-9a-fA-F]+)"/);
if (sessionIDMatch) {
res.sessionID = sessionIDMatch[1];
}

return res;
}
);
12 changes: 12 additions & 0 deletions src/lib/bridge/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import {HasPermissions} from './has_permissions';
import {PingSetupExtension} from './ping_setup_extension';
import {PingExtensionStatus} from './ping_extension_status';
import {PingCancelTrade} from './ping_cancel_trade';
import {CreateTradeOffer} from './create_trade_offer';
import {FetchSteamUser} from './fetch_steam_user';
import {PingTradeStatus} from './ping_trade_status';
import {PingStatus} from './ping_status';
import {FetchOwnInventory} from './fetch_own_inventory';
import {CancelTradeOffer} from './cancel_trade_offer';

export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage,
Expand All @@ -36,4 +42,10 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.PING_SETUP_EXTENSION]: PingSetupExtension,
[RequestType.PING_EXTENSION_STATUS]: PingExtensionStatus,
[RequestType.PING_CANCEL_TRADE]: PingCancelTrade,
[RequestType.CREATE_TRADE_OFFER]: CreateTradeOffer,
[RequestType.FETCH_STEAM_USER]: FetchSteamUser,
[RequestType.PING_TRADE_STATUS]: PingTradeStatus,
[RequestType.PING_STATUS]: PingStatus,
[RequestType.FETCH_OWN_INVENTORY]: FetchOwnInventory,
[RequestType.CANCEL_TRADE_OFFER]: CancelTradeOffer,
};
33 changes: 33 additions & 0 deletions src/lib/bridge/handlers/ping_status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {gStore} from '../../storage/store';
import {StorageKey} from '../../storage/keys';
import {PING_CSFLOAT_TRADE_STATUS_ALARM_NAME} from '../../alarms/csfloat_trade_pings';

export interface PingStatusRequest {}

export interface PingStatusResponse {
last_ping_ms?: number;
next_ping_ms?: number;
}

export const PingStatus = new SimpleHandler<PingStatusRequest, PingStatusResponse>(
RequestType.PING_STATUS,
async (req) => {
const resp: PingStatusResponse = {};

const lastPing = await gStore.getWithStorage<number>(chrome.storage.local, StorageKey.LAST_TRADE_PING_ATTEMPT);
if (lastPing) {
resp.last_ping_ms = lastPing;
}

if (chrome.alarms) {
const alarm = await chrome.alarms.get(PING_CSFLOAT_TRADE_STATUS_ALARM_NAME);
if (alarm) {
resp.next_ping_ms = alarm.scheduledTime;
}
}

return resp;
}
);
19 changes: 19 additions & 0 deletions src/lib/bridge/handlers/ping_trade_status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';
import {pingTradeStatus} from '../../alarms/csfloat_trade_pings';

export interface PingTradeStatusRequest {
// Steam ID we're expecting to fulfill the ping for.
steam_id: string;
}

export interface PingTradeStatusResponse {}

export const PingTradeStatus = new SimpleHandler<PingTradeStatusRequest, PingTradeStatusResponse>(
RequestType.PING_TRADE_STATUS,
async (req) => {
await pingTradeStatus(req.steam_id);

return {};
}
);
Loading

0 comments on commit e0de1e6

Please sign in to comment.