Skip to content

Commit

Permalink
Merge pull request #201 from csfloat/feature/auto-trading-cookies
Browse files Browse the repository at this point in the history
Implements Auto Trading with Cookies
  • Loading branch information
Step7750 authored Apr 4, 2024
2 parents 9682318 + 0d65aad commit d27be8e
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 13 deletions.
1 change: 1 addition & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"type": "module"
},
"permissions": ["storage", "scripting"],
"optional_permissions": ["cookies", "alarms"],
"host_permissions": [
"*://*.steamcommunity.com/market/listings/730/*",
"*://*.steamcommunity.com/id/*/inventory*",
Expand Down
17 changes: 17 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {Handle} from './lib/bridge/server';
import {InternalResponseBundle} from './lib/bridge/types';
import MessageSender = chrome.runtime.MessageSender;
import {ClientSend} from './lib/bridge/client';
import {SendCookies} from './lib/bridge/handlers/send_cookies';
import {setupCookieAlarm} from './lib/utils/alarm';

function unifiedHandler(request: any, sender: MessageSender, sendResponse: (response?: any) => void) {
Handle(request, sender)
Expand All @@ -20,7 +23,17 @@ function unifiedHandler(request: any, sender: MessageSender, sendResponse: (resp
});
}

function requestPermissions(permissions: string[], sendResponse: any) {
chrome.permissions.request({permissions}, (granted) => sendResponse(granted));

return true;
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message === 'requestPermissions') {
return requestPermissions(request.permissions, sendResponse);
}

unifiedHandler(request, sender, sendResponse);
return true;
});
Expand All @@ -29,3 +42,7 @@ chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) =>
unifiedHandler(request, sender, sendResponse);
return true;
});

chrome.runtime.onInstalled.addListener(async ({reason}) => {
await setupCookieAlarm(true);
});
29 changes: 16 additions & 13 deletions src/lib/bridge/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {InternalRequestBundle, InternalResponseBundle, RequestHandler, Version}
import {isFirefox, runtimeNamespace} from '../utils/detect';
import {inPageContext} from '../utils/snips';
import {g_PostMessageBus} from '../bus/post_message_bus';
import {DeferredPromise} from '../utils/deferred_promise';

function canUseSendMessage() {
// Not supported in Firefox Page Context
Expand All @@ -22,21 +23,23 @@ export async function ClientSend<Req, Resp>(handler: RequestHandler<Req, Resp>,
};

if (canUseSendMessage()) {
return new Promise((resolve, reject) => {
const promise = new DeferredPromise<Resp>();

// @ts-ignore Bad types
runtimeNamespace().runtime.sendMessage(
(typeof window !== 'undefined' && window.CSFLOAT_EXTENSION_ID) || chrome.runtime.id,
bundle,
// @ts-ignore Bad types
runtimeNamespace().runtime.sendMessage(
window.CSFLOAT_EXTENSION_ID || chrome.runtime.id,
bundle,
// @ts-ignore Bad types
(resp: InternalResponseBundle) => {
if (resp?.response) {
resolve(resp.response);
} else {
reject(resp?.error);
}
(resp: InternalResponseBundle) => {
if (resp?.response) {
promise.resolve(resp.response);
} else {
promise.reject(resp?.error);
}
);
});
}
);

return promise.promise();
} else {
// Fallback to postmessage bus for browsers that don't implement
// specs fully
Expand Down
4 changes: 4 additions & 0 deletions src/lib/bridge/handlers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {RequestType} from './types';
import {FetchExtensionFile} from './fetch_extension_file';
import {AnnotateOffer} from './annotate_offer';
import {ExtensionVersion} from './extension_version';
import {SendCookies} from './send_cookies';
import {HasPermissions} from './has_permissions';

export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage,
Expand All @@ -26,4 +28,6 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
[RequestType.FETCH_EXTENSION_FILE]: FetchExtensionFile,
[RequestType.ANNOTATE_OFFER]: AnnotateOffer,
[RequestType.EXTENSION_VERSION]: ExtensionVersion,
[RequestType.SEND_COOKIES]: SendCookies,
[RequestType.HAS_PERMISSIONS]: HasPermissions,
};
24 changes: 24 additions & 0 deletions src/lib/bridge/handlers/has_permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {SimpleHandler} from './main';
import {RequestType} from './types';

export interface HasPermissionsRequest {
permissions: string[];
}

export interface HasPermissionsResponse {
granted: boolean;
}

export const HasPermissions = new SimpleHandler<HasPermissionsRequest, HasPermissionsResponse>(
RequestType.HAS_PERMISSIONS,
async (req) => {
// @ts-ignore
const granted = (await chrome.permissions.contains({
permissions: req.permissions,
})) as boolean;

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

export interface SendCookiesRequest {}

export interface SendCookiesResponse {}

export const SendCookies = new SimpleHandler<SendCookiesRequest, SendCookiesResponse>(
RequestType.SEND_COOKIES,
async (req) => {
const cookies = await chrome.cookies.getAll({
domain: 'steamcommunity.com',
});

// For use in verifying trades on CSFloat, opt-in
const formatted = cookies
.filter((e) => {
return [
'timezoneOffset',
'Steam_Language',
'browserid',
'sessionid',
'steamCountry',
'steamLoginSecure',
].includes(e.name);
})
.map((e) => {
return {
name: e.name,
value: e.value,
expiration: e.expirationDate,
};
});

const resp = await fetch(`https://csfloat.com/api/v1/me/steam-cookies`, {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cookies: formatted,
}),
});

// Check if an alarm is setup
await setupCookieAlarm();

return {} as SendCookiesResponse;
}
);
2 changes: 2 additions & 0 deletions src/lib/bridge/handlers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export enum RequestType {
FETCH_EXTENSION_FILE,
ANNOTATE_OFFER,
EXTENSION_VERSION,
SEND_COOKIES,
HAS_PERMISSIONS,
}
92 changes: 92 additions & 0 deletions src/lib/components/trade_offers/auto_track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {css, html} from 'lit';

import {CustomElement, InjectAfter, InjectionMode} from '../injectors';
import {FloatElement} from '../custom';
import '../common/ui/steam-button';
import {ClientSend} from '../../bridge/client';
import {state} from 'lit/decorators.js';
import {FetchPendingTrades} from '../../bridge/handlers/fetch_pending_trades';
import {HasPermissions} from '../../bridge/handlers/has_permissions';

@CustomElement()
@InjectAfter(
'.maincontent .profile_leftcol .nonresponsive_hidden:not(.responsive_createtradeoffer)',
InjectionMode.ONCE
)
export class AutoTrackWidget extends FloatElement {
@state()
show = false;

static styles = [
...FloatElement.styles,
css`
.container {
margin-top: 10px;
margin-bottom: 10px;
padding: 15px;
background-color: rgb(48, 48, 48);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.container.warning {
background-color: rgb(179, 0, 0);
}
.float-icon {
float: left;
}
.item-name {
font-size: 18px;
margin-left: 15px;
line-height: 32px;
}
.sale-info {
padding-left: 45px;
color: darkgrey;
}
`,
];

async connectedCallback() {
super.connectedCallback();

try {
await ClientSend(FetchPendingTrades, {});

const hasPermissions = await ClientSend(HasPermissions, {permissions: ['cookies', 'alarms']});
if (!hasPermissions) {
this.show = true;
}
} catch (e) {
console.info('user is not logged into CSFloat');
}
}

render() {
return this.show
? html`
<div class="container" style="margin: 20px 0 20px 0;">
<div>
<div class="float-icon">
<img
src="https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/79/798a12316637ad8fbb91ddb7dc63f770b680bd19_full.jpg"
style="height: 32px;"
/>
</div>
<span class="item-name"> Automatically Track Offers </span>
<div class="sale-info">Allow CSFloat Market to automatically track and create offers.</div>
</div>
<csfloat-steam-button
id="csfloat-enable-tracking"
.text="${'Enable Tracking'}"
></csfloat-steam-button>
</div>
`
: html``;
}
}
37 changes: 37 additions & 0 deletions src/lib/page_scripts/trade_offers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,43 @@
import {init} from './utils';
import '../components/trade_offers/offer_id';
import '../components/trade_offers/auto_track';
import {inPageContext} from '../utils/snips';
import {ClientSend} from '../bridge/client';
import {SendCookies} from '../bridge/handlers/send_cookies';

init('src/lib/page_scripts/trade_offers.js', main);

function main() {}

if (!inPageContext()) {
const refresh = setInterval(() => {
const widget = document.getElementsByTagName('csfloat-auto-track-widget');
if (!widget || widget.length === 0) {
return;
}

const btn = widget[0]?.shadowRoot?.getElementById('csfloat-enable-tracking');
if (!btn) {
return;
}

btn.addEventListener('click', async () => {
chrome.runtime.sendMessage(
{
message: 'requestPermissions',
permissions: ['cookies', 'alarms'],
},
(granted) => {
if (granted) {
ClientSend(SendCookies, {});
widget[0].parentElement?.removeChild(widget[0]);
} else {
alert('Failed to obtain permissions');
}
}
);
});

clearInterval(refresh);
}, 500);
}
42 changes: 42 additions & 0 deletions src/lib/utils/alarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {SendCookies} from '../bridge/handlers/send_cookies';

const COOKIE_ALARM_NAME = 'send-cookie-alarm';

// MUST be called from the background script
export async function setupCookieAlarm(initial = false) {
// @ts-ignore
const granted = (await chrome.permissions.contains({
permissions: ['alarms', 'cookies'],
})) as boolean;

if (!granted || !chrome.alarms) {
return;
}

const existingAlarm = await chrome.alarms.get(COOKIE_ALARM_NAME);
if (existingAlarm) {
if (initial) {
createAlarmListener();
}

// Already exists, return
return;
}

await chrome.alarms.create(COOKIE_ALARM_NAME, {
delayInMinutes: 1,
periodInMinutes: 60 * 6, // 6 hours
});

createAlarmListener();
}

function createAlarmListener() {
chrome.alarms?.onAlarm?.addListener(async (alarm) => {
if (alarm.name !== COOKIE_ALARM_NAME) {
return;
}

await SendCookies.handleRequest({}, {});
});
}

0 comments on commit d27be8e

Please sign in to comment.