Skip to content

Commit d27be8e

Browse files
authored
Merge pull request #201 from csfloat/feature/auto-trading-cookies
Implements Auto Trading with Cookies
2 parents 9682318 + 0d65aad commit d27be8e

File tree

10 files changed

+287
-13
lines changed

10 files changed

+287
-13
lines changed

manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"type": "module"
4545
},
4646
"permissions": ["storage", "scripting"],
47+
"optional_permissions": ["cookies", "alarms"],
4748
"host_permissions": [
4849
"*://*.steamcommunity.com/market/listings/730/*",
4950
"*://*.steamcommunity.com/id/*/inventory*",

src/background.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {Handle} from './lib/bridge/server';
22
import {InternalResponseBundle} from './lib/bridge/types';
33
import MessageSender = chrome.runtime.MessageSender;
4+
import {ClientSend} from './lib/bridge/client';
5+
import {SendCookies} from './lib/bridge/handlers/send_cookies';
6+
import {setupCookieAlarm} from './lib/utils/alarm';
47

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

26+
function requestPermissions(permissions: string[], sendResponse: any) {
27+
chrome.permissions.request({permissions}, (granted) => sendResponse(granted));
28+
29+
return true;
30+
}
31+
2332
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
33+
if (request.message === 'requestPermissions') {
34+
return requestPermissions(request.permissions, sendResponse);
35+
}
36+
2437
unifiedHandler(request, sender, sendResponse);
2538
return true;
2639
});
@@ -29,3 +42,7 @@ chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) =>
2942
unifiedHandler(request, sender, sendResponse);
3043
return true;
3144
});
45+
46+
chrome.runtime.onInstalled.addListener(async ({reason}) => {
47+
await setupCookieAlarm(true);
48+
});

src/lib/bridge/client.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {InternalRequestBundle, InternalResponseBundle, RequestHandler, Version}
22
import {isFirefox, runtimeNamespace} from '../utils/detect';
33
import {inPageContext} from '../utils/snips';
44
import {g_PostMessageBus} from '../bus/post_message_bus';
5+
import {DeferredPromise} from '../utils/deferred_promise';
56

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

2425
if (canUseSendMessage()) {
25-
return new Promise((resolve, reject) => {
26+
const promise = new DeferredPromise<Resp>();
27+
28+
// @ts-ignore Bad types
29+
runtimeNamespace().runtime.sendMessage(
30+
(typeof window !== 'undefined' && window.CSFLOAT_EXTENSION_ID) || chrome.runtime.id,
31+
bundle,
2632
// @ts-ignore Bad types
27-
runtimeNamespace().runtime.sendMessage(
28-
window.CSFLOAT_EXTENSION_ID || chrome.runtime.id,
29-
bundle,
30-
// @ts-ignore Bad types
31-
(resp: InternalResponseBundle) => {
32-
if (resp?.response) {
33-
resolve(resp.response);
34-
} else {
35-
reject(resp?.error);
36-
}
33+
(resp: InternalResponseBundle) => {
34+
if (resp?.response) {
35+
promise.resolve(resp.response);
36+
} else {
37+
promise.reject(resp?.error);
3738
}
38-
);
39-
});
39+
}
40+
);
41+
42+
return promise.promise();
4043
} else {
4144
// Fallback to postmessage bus for browsers that don't implement
4245
// specs fully

src/lib/bridge/handlers/handlers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {RequestType} from './types';
1212
import {FetchExtensionFile} from './fetch_extension_file';
1313
import {AnnotateOffer} from './annotate_offer';
1414
import {ExtensionVersion} from './extension_version';
15+
import {SendCookies} from './send_cookies';
16+
import {HasPermissions} from './has_permissions';
1517

1618
export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
1719
[RequestType.EXECUTE_SCRIPT_ON_PAGE]: ExecuteScriptOnPage,
@@ -26,4 +28,6 @@ export const HANDLERS_MAP: {[key in RequestType]: RequestHandler<any, any>} = {
2628
[RequestType.FETCH_EXTENSION_FILE]: FetchExtensionFile,
2729
[RequestType.ANNOTATE_OFFER]: AnnotateOffer,
2830
[RequestType.EXTENSION_VERSION]: ExtensionVersion,
31+
[RequestType.SEND_COOKIES]: SendCookies,
32+
[RequestType.HAS_PERMISSIONS]: HasPermissions,
2933
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {SimpleHandler} from './main';
2+
import {RequestType} from './types';
3+
4+
export interface HasPermissionsRequest {
5+
permissions: string[];
6+
}
7+
8+
export interface HasPermissionsResponse {
9+
granted: boolean;
10+
}
11+
12+
export const HasPermissions = new SimpleHandler<HasPermissionsRequest, HasPermissionsResponse>(
13+
RequestType.HAS_PERMISSIONS,
14+
async (req) => {
15+
// @ts-ignore
16+
const granted = (await chrome.permissions.contains({
17+
permissions: req.permissions,
18+
})) as boolean;
19+
20+
return {
21+
granted,
22+
};
23+
}
24+
);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {SimpleHandler} from './main';
2+
import {RequestType} from './types';
3+
import {setupCookieAlarm} from '../../utils/alarm';
4+
5+
export interface SendCookiesRequest {}
6+
7+
export interface SendCookiesResponse {}
8+
9+
export const SendCookies = new SimpleHandler<SendCookiesRequest, SendCookiesResponse>(
10+
RequestType.SEND_COOKIES,
11+
async (req) => {
12+
const cookies = await chrome.cookies.getAll({
13+
domain: 'steamcommunity.com',
14+
});
15+
16+
// For use in verifying trades on CSFloat, opt-in
17+
const formatted = cookies
18+
.filter((e) => {
19+
return [
20+
'timezoneOffset',
21+
'Steam_Language',
22+
'browserid',
23+
'sessionid',
24+
'steamCountry',
25+
'steamLoginSecure',
26+
].includes(e.name);
27+
})
28+
.map((e) => {
29+
return {
30+
name: e.name,
31+
value: e.value,
32+
expiration: e.expirationDate,
33+
};
34+
});
35+
36+
const resp = await fetch(`https://csfloat.com/api/v1/me/steam-cookies`, {
37+
credentials: 'include',
38+
method: 'POST',
39+
headers: {
40+
'Content-Type': 'application/json',
41+
},
42+
body: JSON.stringify({
43+
cookies: formatted,
44+
}),
45+
});
46+
47+
// Check if an alarm is setup
48+
await setupCookieAlarm();
49+
50+
return {} as SendCookiesResponse;
51+
}
52+
);

src/lib/bridge/handlers/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ export enum RequestType {
1111
FETCH_EXTENSION_FILE,
1212
ANNOTATE_OFFER,
1313
EXTENSION_VERSION,
14+
SEND_COOKIES,
15+
HAS_PERMISSIONS,
1416
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import {css, html} from 'lit';
2+
3+
import {CustomElement, InjectAfter, InjectionMode} from '../injectors';
4+
import {FloatElement} from '../custom';
5+
import '../common/ui/steam-button';
6+
import {ClientSend} from '../../bridge/client';
7+
import {state} from 'lit/decorators.js';
8+
import {FetchPendingTrades} from '../../bridge/handlers/fetch_pending_trades';
9+
import {HasPermissions} from '../../bridge/handlers/has_permissions';
10+
11+
@CustomElement()
12+
@InjectAfter(
13+
'.maincontent .profile_leftcol .nonresponsive_hidden:not(.responsive_createtradeoffer)',
14+
InjectionMode.ONCE
15+
)
16+
export class AutoTrackWidget extends FloatElement {
17+
@state()
18+
show = false;
19+
20+
static styles = [
21+
...FloatElement.styles,
22+
css`
23+
.container {
24+
margin-top: 10px;
25+
margin-bottom: 10px;
26+
padding: 15px;
27+
background-color: rgb(48, 48, 48);
28+
color: white;
29+
display: flex;
30+
justify-content: space-between;
31+
align-items: center;
32+
}
33+
34+
.container.warning {
35+
background-color: rgb(179, 0, 0);
36+
}
37+
38+
.float-icon {
39+
float: left;
40+
}
41+
42+
.item-name {
43+
font-size: 18px;
44+
margin-left: 15px;
45+
line-height: 32px;
46+
}
47+
48+
.sale-info {
49+
padding-left: 45px;
50+
color: darkgrey;
51+
}
52+
`,
53+
];
54+
55+
async connectedCallback() {
56+
super.connectedCallback();
57+
58+
try {
59+
await ClientSend(FetchPendingTrades, {});
60+
61+
const hasPermissions = await ClientSend(HasPermissions, {permissions: ['cookies', 'alarms']});
62+
if (!hasPermissions) {
63+
this.show = true;
64+
}
65+
} catch (e) {
66+
console.info('user is not logged into CSFloat');
67+
}
68+
}
69+
70+
render() {
71+
return this.show
72+
? html`
73+
<div class="container" style="margin: 20px 0 20px 0;">
74+
<div>
75+
<div class="float-icon">
76+
<img
77+
src="https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/79/798a12316637ad8fbb91ddb7dc63f770b680bd19_full.jpg"
78+
style="height: 32px;"
79+
/>
80+
</div>
81+
<span class="item-name"> Automatically Track Offers </span>
82+
<div class="sale-info">Allow CSFloat Market to automatically track and create offers.</div>
83+
</div>
84+
<csfloat-steam-button
85+
id="csfloat-enable-tracking"
86+
.text="${'Enable Tracking'}"
87+
></csfloat-steam-button>
88+
</div>
89+
`
90+
: html``;
91+
}
92+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
import {init} from './utils';
22
import '../components/trade_offers/offer_id';
3+
import '../components/trade_offers/auto_track';
4+
import {inPageContext} from '../utils/snips';
5+
import {ClientSend} from '../bridge/client';
6+
import {SendCookies} from '../bridge/handlers/send_cookies';
37

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

610
function main() {}
11+
12+
if (!inPageContext()) {
13+
const refresh = setInterval(() => {
14+
const widget = document.getElementsByTagName('csfloat-auto-track-widget');
15+
if (!widget || widget.length === 0) {
16+
return;
17+
}
18+
19+
const btn = widget[0]?.shadowRoot?.getElementById('csfloat-enable-tracking');
20+
if (!btn) {
21+
return;
22+
}
23+
24+
btn.addEventListener('click', async () => {
25+
chrome.runtime.sendMessage(
26+
{
27+
message: 'requestPermissions',
28+
permissions: ['cookies', 'alarms'],
29+
},
30+
(granted) => {
31+
if (granted) {
32+
ClientSend(SendCookies, {});
33+
widget[0].parentElement?.removeChild(widget[0]);
34+
} else {
35+
alert('Failed to obtain permissions');
36+
}
37+
}
38+
);
39+
});
40+
41+
clearInterval(refresh);
42+
}, 500);
43+
}

src/lib/utils/alarm.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {SendCookies} from '../bridge/handlers/send_cookies';
2+
3+
const COOKIE_ALARM_NAME = 'send-cookie-alarm';
4+
5+
// MUST be called from the background script
6+
export async function setupCookieAlarm(initial = false) {
7+
// @ts-ignore
8+
const granted = (await chrome.permissions.contains({
9+
permissions: ['alarms', 'cookies'],
10+
})) as boolean;
11+
12+
if (!granted || !chrome.alarms) {
13+
return;
14+
}
15+
16+
const existingAlarm = await chrome.alarms.get(COOKIE_ALARM_NAME);
17+
if (existingAlarm) {
18+
if (initial) {
19+
createAlarmListener();
20+
}
21+
22+
// Already exists, return
23+
return;
24+
}
25+
26+
await chrome.alarms.create(COOKIE_ALARM_NAME, {
27+
delayInMinutes: 1,
28+
periodInMinutes: 60 * 6, // 6 hours
29+
});
30+
31+
createAlarmListener();
32+
}
33+
34+
function createAlarmListener() {
35+
chrome.alarms?.onAlarm?.addListener(async (alarm) => {
36+
if (alarm.name !== COOKIE_ALARM_NAME) {
37+
return;
38+
}
39+
40+
await SendCookies.handleRequest({}, {});
41+
});
42+
}

0 commit comments

Comments
 (0)