Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/lib/alarms/csfloat_trade_pings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {describe, expect, it, vi} from 'vitest';
import {HasPermissions} from '../bridge/handlers/has_permissions';
import {pingTradeStatus} from './csfloat_trade_pings';

vi.hoisted(() => vi.stubGlobal('chrome', {storage: {local: {}}}));
vi.mock('../storage/store', () => ({gStore: {setWithStorage: vi.fn()}}));
vi.mock('../bridge/handlers/has_permissions', () => ({
HasPermissions: {handleRequest: vi.fn().mockResolvedValue({granted: false})},
}));

describe('pingTradeStatus', () => {
it('shares one run between overlapping background triggers, but not page bridge calls', async () => {
await Promise.all([pingTradeStatus(), pingTradeStatus()]);
expect(HasPermissions.handleRequest).toHaveBeenCalledTimes(1);

await Promise.all([pingTradeStatus(), pingTradeStatus('76561198000000000')]);
expect(HasPermissions.handleRequest).toHaveBeenCalledTimes(3);
});
});
20 changes: 19 additions & 1 deletion src/lib/alarms/csfloat_trade_pings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,25 @@ import {FetchSlimTrades} from '../bridge/handlers/fetch_slim_trades';

export const PING_CSFLOAT_TRADE_STATUS_ALARM_NAME = 'ping_csfloat_trade_status_alarm';

export async function pingTradeStatus(expectedSteamID?: string) {
let inFlightPing: Promise<void> | null = null;

/**
* Shares one run between overlapping background triggers (alarm, service worker wake-up) so they don't
* duplicate the same pings and notary proofs. Page bridge calls pass a steam ID and run on their own, since
* joining an in-flight run would ignore that steam ID and return trade data fetched before the user's action.
*/
export function pingTradeStatus(expectedSteamID?: string): Promise<void> {
if (expectedSteamID) {
return runPingTradeStatus(expectedSteamID);
}

inFlightPing ??= runPingTradeStatus().finally(() => {
inFlightPing = null;
});
return inFlightPing;
}
Comment thread
Step7750 marked this conversation as resolved.

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

const hasPermissions = await HasPermissions.handleRequest(
Expand Down
1 change: 1 addition & 0 deletions src/lib/alarms/notary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {isBackgroundNotaryOfferStateEnabled, isBackgroundNotaryRollbackEnabled}

vi.mock('../bridge/handlers/fetch_notary_meta', () => ({FetchNotaryMeta: {handleRequest: vi.fn()}}));
vi.mock('../utils/detect', () => ({isFirefox: () => false}));
vi.stubGlobal('chrome', {offscreen: {Reason: {WORKERS: 'WORKERS'}}});

describe('background notary meta', () => {
it('caches the meta for 30 minutes, but not failed fetches', async () => {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/alarms/notary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export function isBackgroundNotaryOfferStateEnabled(): Promise<boolean> {
}

async function isBackgroundNotaryEnabled(setting: keyof NotaryMeta): Promise<boolean> {
if (isFirefox()) {
// Some older Chrome versions don't define the offscreen WORKERS reason, so the offscreen document can't be created
if (isFirefox() || !chrome.offscreen?.Reason?.WORKERS) {
return false;
}

Expand Down
3 changes: 2 additions & 1 deletion src/lib/bridge/handlers/notary_prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ export const NotaryProve = MaxConcurrency(
};
}
),
2
// The offscreen document is periodically closed after a proof responds, which kills any other in-flight proof
1
);
6 changes: 1 addition & 5 deletions src/offscreen/handlers/notary_prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {getSteamRequestURL} from '../../lib/notary/utils';
import * as Comlink from 'comlink';
import {environment} from '../../environment';
import type {IoChannel, LoggingLevel, Method, Prover as TProver, ProverMode, Reveal} from '@csfloat/tlsn-wasm';
import {NotarySessionClient} from './notary_session_client';
import {NotarySessionClient, toWebSocketUrl} from './notary_session_client';
import {Remote} from 'comlink';
import {wait} from '../../lib/utils/snips';
import {WebSocketIoChannel} from '../io_channel';
Expand Down Expand Up @@ -146,10 +146,6 @@ export const TLSNProveOffscreenHandler = new ClosableOffscreenHandler<
}
);

function toWebSocketUrl(url: string): string {
return url.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');
}

/**
* Estimates the total request byte size over the wire if sent over HTTP 1.1
*
Expand Down
7 changes: 6 additions & 1 deletion src/offscreen/handlers/notary_session_client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type {VerificationResults} from './types';
import {environment} from '../../environment';

// Older Chrome versions reject http(s) URLs in the WebSocket constructor
export function toWebSocketUrl(url: string): string {
return url.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:');
}

type ClientMessage = {
type: 'register';
maxRecvData: number;
Expand Down Expand Up @@ -47,7 +52,7 @@ export class NotarySessionClient {
}

private static buildSessionUrl(token?: string): string {
let url = `${environment.notary.tlsn}/session`;
let url = toWebSocketUrl(`${environment.notary.tlsn}/session`);
if (token) {
url += `?token=${token}`;
}
Expand Down
54 changes: 26 additions & 28 deletions src/offscreen/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,36 @@ const OFFSCREEN_DOCUMENT_PATH = '/src/offscreen.html';
let creating: Promise<void> | null;

export async function openOffscreenDocument(): Promise<void> {
const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH);
const hasExistingContext = await chrome.offscreen.hasDocument();
// Shared so a concurrent caller can't see the document before it has registered its message listener
creating ??= createOffscreenDocument().finally(() => {
creating = null;
});
await creating;
}

if (hasExistingContext) {
async function createOffscreenDocument(): Promise<void> {
if (await chrome.offscreen.hasDocument()) {
return;
}

if (creating) {
await creating;
} else {
creating = (async () => {
const ready = new Promise<void>((resolve) => {
const listener = (request: any, sender: chrome.runtime.MessageSender) => {
if (request?.type === 'offscreen_ready' && sender.url === offscreenUrl) {
chrome.runtime.onMessage.removeListener(listener);
resolve();
}
};
chrome.runtime.onMessage.addListener(listener);
});

await chrome.offscreen.createDocument({
url: OFFSCREEN_DOCUMENT_PATH,
reasons: [chrome.offscreen.Reason.WORKERS],
justification: 'Workers for multi-threading',
});

await ready;
})();
await creating;
creating = null;
}
const offscreenUrl = chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH);
const ready = new Promise<void>((resolve) => {
const listener = (request: any, sender: chrome.runtime.MessageSender) => {
if (request?.type === 'offscreen_ready' && sender.url === offscreenUrl) {
chrome.runtime.onMessage.removeListener(listener);
resolve();
}
};
chrome.runtime.onMessage.addListener(listener);
});

await chrome.offscreen.createDocument({
url: OFFSCREEN_DOCUMENT_PATH,
reasons: [chrome.offscreen.Reason.WORKERS],
justification: 'Workers for multi-threading',
});

await ready;
}

export async function closeOffscreenDocument(): Promise<void> {
Expand Down
Loading