From 95ce63789281b0d842bd87b94537ee6c46350920 Mon Sep 17 00:00:00 2001 From: xenticore Date: Thu, 1 May 2025 15:27:14 -0400 Subject: [PATCH] feat: enable webextension builds Currently only supports Chrome due to no `chrome` -> `browser` polyfill --- .gitea/workflows/build.yaml | 5 +- .gitea/workflows/release.yaml | 18 +++-- bun.lockb | Bin 196987 -> 215301 bytes package.json | 4 ++ src/background/background.ts | 111 +++++++++++++++++++++++++++++ src/content/content.ts | 6 +- src/content/exchangeRateService.ts | 45 ++++++++---- src/content/fetchWrap.ts | 10 +++ src/content/priceService.ts | 9 ++- src/content/pricing/pricestf.ts | 13 ++-- src/content/schemaService.ts | 26 ++++--- src/content/storage.ts | 5 +- src/manifest.json | 20 ++++++ tsconfig.json | 2 +- webpack.config.js | 60 +++++++++++++--- 15 files changed, 281 insertions(+), 53 deletions(-) create mode 100644 src/background/background.ts create mode 100644 src/content/fetchWrap.ts diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 349d978..380ea99 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -30,6 +30,5 @@ jobs: - name: Archive production artifacts uses: actions/upload-artifact@v3 with: - name: tf2wikipricing.user.js - path: | - dist/userscript/tf2wikipricing.user.js \ No newline at end of file + name: tf2wikipricing + path: dist/ \ No newline at end of file diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index c071d53..a6cfd0b 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -26,9 +26,8 @@ jobs: - name: Archive production artifacts uses: actions/upload-artifact@v3 with: - name: tf2wikipricing.user.js - path: | - dist/userscript/tf2wikipricing.user.js + name: tf2wikipricing + path: dist/ deploy: runs-on: debian-latest needs: build @@ -36,8 +35,14 @@ jobs: - name: Download release artifacts uses: actions/download-artifact@v3 with: - name: tf2wikipricing.user.js - path: userscript + name: tf2wikipricing + path: dist/ + - name: Package Chrome extension + run: | + cd dist/ + echo "${{ secrets.CRX_PRIVATE_KEY }}" > private.pem + bun x crx pack -p private.pem -o tf2wikipricing.crx extension/ + rm -f private.pem - name: Create release id: use-go-action uses: akkuman/gitea-release-action@v1 @@ -45,5 +50,6 @@ jobs: title: "v${{ need.build.outputs.version }}" name: "v${{ need.build.outputs.version }}" files: | - userscript/** + dist/tf2wikipricing.crx + dist/tf2wikipricing.user.js sha256sum: true \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index c24e42ba28a65cb7ca74cc71392c880a763064e8..a4c615f26f8c1870ce7a98e24342e12a17f77183 100755 GIT binary patch delta 47362 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 { + console.log(response) + return response.json() + }) + .then(json => { + sendResponse(json) + }) + .catch(error => { + console.error("Failed to get exchange rates", error); + }) + return true; + } +}) + +chrome.runtime.onMessage.addListener( + function(request, sender, sendResponse) { + if (request.contentScriptQuery == "querySchema") { + const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json"; + fetch(url) + .then(response => response.json()) + .then(json => sendResponse(json)) + .catch(error => { + console.error("Failed to get schema", error); + }) + return true; + } + } +); + +chrome.runtime.onMessage.addListener( + function(request, sender, sendResponse) { + if (request.contentScriptQuery == "getPricesTFToken") { + fetch('https://api2.prices.tf/auth/access', { + method: 'post', + headers: new Headers({ + 'Accept': 'application/json' + }) + }) + .then(response => response.json()) + .then(json => sendResponse(json['accessToken'])) + .catch(error => { + console.error("Failed to get access token", error); + }) + return true; + } + } +) + +class PricesResponse { + keys: number + metal: number +} + +async function priceUsingPricesTF(token: string, sku: string, retries: number = 3): Promise { + const url = `https://api2.prices.tf/prices/${encodeURIComponent(sku)}`; + const response = await fetch(url, { + method: 'get', + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }) + if (response.status === 404 && sku.includes(';') && !sku.includes(';uncraftable')) { + const quality: number = parseInt(sku.split(';')[1], 10); + if(quality === 6) { + // Try uncraftable variant if unique weapon + return priceUsingPricesTF(token, sku + ';uncraftable'); + } + } + if(response.status === 503) { + // Happens if we send too many requests in a short period of time + // Retry after a few seconds + if(retries >= 0) { + console.log(`Cloudflare rate limit exceeded, trying again after 1 second, ${retries} retries left`) + await new Promise(resolve => setTimeout(resolve, 1000)); + return priceUsingPricesTF(token, sku, retries - 1); + } else { + throw new Error(`Cloudflare rate limit exceeded, stopping`) + } + } + const data = await response.json(); + const prices = new PricesResponse(); + prices.keys = data['sellKeys'] + prices.metal = data['sellHalfScrap'] / 18.0; + return prices; +} + +chrome.runtime.onMessage.addListener( + function(request, sender, sendResponse) { + if (request.contentScriptQuery == "priceSKU") { + const sku: string = request.sku + const service: string = request.service + const token: string = request.token + switch (service) { + case "prices.tf": { + priceUsingPricesTF(token, sku).then((response) => { + sendResponse(JSON.stringify(response)); + }) + return true; + } + default: + return false; + } + } + } +); \ No newline at end of file diff --git a/src/content/content.ts b/src/content/content.ts index f486c98..955f2ea 100644 --- a/src/content/content.ts +++ b/src/content/content.ts @@ -1,4 +1,5 @@ -import styleCss from './style.css' +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; import { logDebug, log, logError } from './utils/log' import { getPricesToken } from './pricing/pricestf' @@ -417,7 +418,8 @@ async function inject() { } function addStyles() { - const head = document.head || document.getElementsByTagName('head')[0], + if(__ENV_USERSCRIPT) { + const head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); head.appendChild(style); style.innerHTML = styleCss; diff --git a/src/content/exchangeRateService.ts b/src/content/exchangeRateService.ts index 5b41de7..ba6a645 100644 --- a/src/content/exchangeRateService.ts +++ b/src/content/exchangeRateService.ts @@ -1,8 +1,9 @@ import { getStorageValue, setStorageValue } from './storage' import { logDebug, log, logError } from './utils/log' import { storage_exchangerates, storage_exchangerates_next, storage_exchangerates_update } from './config' -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import './GM_fetch' +import { fetchWrap } from './fetchWrap'; +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; export interface ExchangeRates { [key: string]: number; @@ -26,7 +27,7 @@ export async function prepareExchangeRates(): Promise { const lastUpdateTime = new Date(update); const nextUpdateTime = new Date(nextUpdate); log(`Exchange rates updated at ${lastUpdateTime}`); - if (rates == null || Object.keys(rates).length === 0 || lastUpdateTime.getTime() > nextUpdateTime.getTime()) { + if (rates == null || Object.keys(rates).length === 0 || Date.now() > nextUpdateTime.getTime()) { needsUpdate = true } } else { @@ -35,19 +36,35 @@ export async function prepareExchangeRates(): Promise { if(needsUpdate) { log("Exchange rates out of Date. Rebuilding..."); - const url = "https://open.er-api.com/v6/latest/USD" - const response = await GM_fetch(url); - if (response.ok) { - await setStorageValue(storage_exchangerates_update, new Date().toISOString()) - const json = await response.json() - if(json != null){ - rates = json['rates'] - await setStorageValue(storage_exchangerates, rates) - await setStorageValue(storage_exchangerates_next, json['time_next_update_utc']) + let exchangeResponse: { + rates: ExchangeRates, + time_next_update_utc: string + } + if(__ENV_USERSCRIPT) { + const url = "https://open.er-api.com/v6/latest/USD" + try { + const response: Response = await fetchWrap(url) + if(response.ok) { + exchangeResponse = await response.json() + } + } catch (e) { + logDebug(e); + throw e; } - logDebug(`Exchange rates updated at ${new Date()}`) } else { - logError(`Failed to fetch exchange rates. Status code: ${response.status}`, response) + exchangeResponse = await chrome.runtime.sendMessage({contentScriptQuery: "queryExchangeRates"}) + } + try { + if(exchangeResponse == null) { + throw new Error("Rates are null") + } + rates = exchangeResponse.rates + await setStorageValue(storage_exchangerates_update, new Date().toISOString()) + await setStorageValue(storage_exchangerates, exchangeResponse.rates) + await setStorageValue(storage_exchangerates_next, exchangeResponse.time_next_update_utc) + logDebug(`Exchange rates updated at ${new Date()}`) + } catch(e) { + logError(`Failed to store exchange rates.`, e) } } diff --git a/src/content/fetchWrap.ts b/src/content/fetchWrap.ts new file mode 100644 index 0000000..49a30ce --- /dev/null +++ b/src/content/fetchWrap.ts @@ -0,0 +1,10 @@ +declare let __ENV_USERSCRIPT: boolean; +declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise + +export function fetchWrap(input: string | URL | globalThis.Request, init?: RequestInit): Promise { + if(__ENV_USERSCRIPT) { + return GM_fetch(input, init) + } else { + return fetch(input, init) + } +} diff --git a/src/content/priceService.ts b/src/content/priceService.ts index efa0a1a..4036c8c 100644 --- a/src/content/priceService.ts +++ b/src/content/priceService.ts @@ -2,6 +2,8 @@ import { defindex_key, storage_priceprefix } from "./config" import { priceUsingPricesTF } from "./pricing/pricestf" import { getStorageValue, setStorageValue } from "./storage" import { logDebug } from "./utils/log" +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; /** Pricing data for a given TF2 item. */ export class ItemPriceData { @@ -58,7 +60,12 @@ export async function fetchPrice(token: string, sku: string, update: Date = new data.ttl = ttl try { - const response = await priceUsingPricesTF(token, sku) + let response: PricesResponse + if(__ENV_USERSCRIPT) { + response = await priceUsingPricesTF(token, sku) + } else { + response = JSON.parse(await chrome.runtime.sendMessage({contentScriptQuery: "priceSKU", service: "prices.tf", sku: sku, token: token})); + } if (response) { data.keys = response.keys data.metal = response.metal diff --git a/src/content/pricing/pricestf.ts b/src/content/pricing/pricestf.ts index f125ec3..abeff72 100644 --- a/src/content/pricing/pricestf.ts +++ b/src/content/pricing/pricestf.ts @@ -1,10 +1,12 @@ -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import '../GM_fetch' +import { fetchWrap } from '../fetchWrap' import { logDebug, logError } from '../utils/log' +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; async function getPricesToken(): Promise { + if(__ENV_USERSCRIPT) { return new Promise((resolve, reject) => { - GM_fetch('https://api2.prices.tf/auth/access', { + fetchWrap('https://api2.prices.tf/auth/access', { method: 'post', headers: new Headers({ 'Accept': 'application/json' @@ -18,6 +20,9 @@ async function getPricesToken(): Promise { }) .then((responseData) => resolve(responseData['accessToken'])) }) + } else { + return chrome.runtime.sendMessage({contentScriptQuery: 'getPricesTFToken'}) + } } class PricesResponse { @@ -43,7 +48,7 @@ async function priceUsingPricesTF(token: string, sku: string, retries: number = // https://api2.prices.tf/prices/${sku} // Authorization: Bearer ${token} try { - const response = await GM_fetch(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { + const response = await fetchWrap(`https://api2.prices.tf/prices/${encodeURIComponent(sku)}`, { method: 'get', headers: { 'Accept': 'application/json', diff --git a/src/content/schemaService.ts b/src/content/schemaService.ts index 47b00d4..1709ff1 100644 --- a/src/content/schemaService.ts +++ b/src/content/schemaService.ts @@ -1,10 +1,11 @@ +declare const __ENV_WEBEXTENSION: boolean; +declare const __ENV_USERSCRIPT: boolean; import { getStorageValue, setStorageValue } from './storage' import { logDebug, log, logError } from './utils/log' import './config' -declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise -import './GM_fetch' import { storage_version, storage_schema, storage_lastUpdateTime } from './config' import Australiums from '../resources/australiums.json' +import { fetchWrap } from './fetchWrap' const semver = require('semver') export function checkAustraliumVariant(defindex: number): boolean { @@ -162,7 +163,7 @@ export async function prepareSchema(): Promise { const storedVersion: string | null = await getStorageValue(storage_version, null) if(!storedVersion || !semver.valid(storedVersion)) { - log(`Cache is from an unknown version of the extension. Updating for version ${__VERSION__}`); + log(`Preparing the extension for the first time.`); needsUpdate = true } else if(semver.valid(storedVersion) && semver.lt(storedVersion, __VERSION__)) { log(`Cache is from a previous version (${storedVersion}) of the extension. Updating for version ${__VERSION__}`); @@ -183,15 +184,22 @@ export async function prepareSchema(): Promise { if(needsUpdate) { log("Item Schema out of Date. Rebuilding..."); - const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" - const response = await GM_fetch(url); - if (response.ok) { + try { await setStorageValue(storage_lastUpdateTime, new Date().getTime()); // eslint-disable-next-line @typescript-eslint/no-explicit-any const cacheItems: any = {} - const responseItems: SchemaResponseItem[] = await response.json() + let responseItems: SchemaResponseItem[]; + if(__ENV_USERSCRIPT) { + const url = "https://raw.githubusercontent.com/danocmx/node-tf2-static-schema/master/static/items.json" + const response = await fetchWrap(url); + if(response.ok) { + responseItems = await response.json(); + } + } else { + responseItems = await chrome.runtime.sendMessage({contentScriptQuery: "querySchema"}) + } // We want to keep the keys `defindex`, `item_name`, and `attributes` responseItems.forEach((item: SchemaResponseItem) => { const defindex: number = item['defindex'] @@ -234,8 +242,8 @@ export async function prepareSchema(): Promise { itemSchema = cacheItems await setStorageValue(storage_version, __VERSION__); logDebug(`Item schema updated at ${new Date()}`) - } else { - logError("Could not fetch item schema."); + } catch (e) { + logError("Could not fetch item schema.", e); } } return itemSchema diff --git a/src/content/storage.ts b/src/content/storage.ts index 756df98..6b1d2b7 100644 --- a/src/content/storage.ts +++ b/src/content/storage.ts @@ -6,7 +6,8 @@ function getStorageValue(name: string, defaultValue: string): Promise { if(__ENV_USERSCRIPT) { return GM.getValue(name, defaultValue); } else if(__ENV_WEBEXTENSION) { - return browser.storage.local.get(name); + return chrome.storage.local.get(name) + .then((result) => result[name]) } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any return new Promise((resolve) => { @@ -19,7 +20,7 @@ function setStorageValue(name: string, value: unknown): Promise { if(__ENV_USERSCRIPT) { return GM.setValue(name, value as GM.Value); } else if(__ENV_WEBEXTENSION) { - return browser.storage.local.set({name, value}); + return chrome.storage.local.set({[name]: value}); } else { return new Promise((_, reject) => { reject(); diff --git a/src/manifest.json b/src/manifest.json index 09c8e80..0d5fd6e 100755 --- a/src/manifest.json +++ b/src/manifest.json @@ -4,14 +4,34 @@ "author": EXTENSION_AUTHOR, "manifest_version": 3, "version": EXTENSION_VERSION, + "permissions": [ + "storage", + "scripting" + ], + "host_permissions": [ + "https://wiki.teamfortress.com/wiki/*", + "https://*.prices.tf/*", + "https://open.er-api.com/*" + ], + "web_accessible_resources": [ + { + "resources": ["lib/style.css", "resources/*"], + "matches": ["https://wiki.teamfortress.com/*"] + } + ], "content_scripts": [ { "matches": ["*://wiki.teamfortress.com/wiki/*"], "run_at": "document_start", "all_frames": true, + "css": ["lib/style.css"], "js": ["content/content.js"] } ], + "background": { + "service_worker": "background/background.js", + "type": "module" + }, "icons": { "48": "icons/icon-48.png", "96": "icons/icon-96.png" diff --git a/tsconfig.json b/tsconfig.json index 8282a68..4b38ead 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,6 @@ "moduleResolution": "node", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "types": ["bun-types", "jest", "greasemonkey", "firefox-webext-browser"] + "types": ["bun-types", "jest", "greasemonkey", "chrome", "firefox-webext-browser"] } } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index a26fc60..b67934b 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ var path = require('path'); var CopyPlugin = require('copy-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); var webpack = require('webpack'); var fs = require('fs'); var package = require('./package.json'); @@ -21,32 +22,66 @@ const defines = { } module.exports = [ - /* // WebExtension { + devtool: "source-map", entry: { - content: './src/content/content.ts' + content: './src/content/content.ts', + background: './src/background/background.ts', + style: './src/content/style.css' }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules|GM_fetch/, }, { - test: /\.(png|jpg|gif|svg)$/i, + test: /\.css$/i, use: [ { - loader: 'url-loader', + loader: MiniCssExtractPlugin.loader, options: { - limit: true, + publicPath: '/', // Adjust if needed for relative path resolution + }, + }, + { + loader: 'css-loader', + options: { + url: true, // Ensures url() in CSS is processed + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-url': { + url: (asset) => { + // Transform relative URLs to extension-style URLs + const relativePath = asset.url.replace(/^\.\.\//, '') // Remove leading ../resources part + return `chrome-extension://__MSG_@@extension_id__/${relativePath}`; + }, + }, + }, + }, }, }, ], }, + { + test: /\.(jpe?g|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, + type: 'asset/resource', + generator: { + filename: 'resources/[name][ext]', + }, + }, ], }, + externals: { + './src/content/GM_fetch': 'commonjs2 null' + }, optimization: { minimize: true }, @@ -55,12 +90,12 @@ module.exports = [ filename: "[name]/[name].js" }, resolve: { - extensions: [".ts", ".tsx", ".js", ".json", ".css"] + extensions: [".ts", ".tsx", ".js", ".json"] }, plugins: [ - new webpack.DefinePlugin({__ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), + new webpack.DefinePlugin({ ...defines, __ENV_WEBEXTENSION: true, __ENV_USERSCRIPT: false}), new CopyPlugin({ patterns: [ - { from: './src/manifest.json', to: 'manifest.json', + { from: './src/manifest.json', to: 'manifest.json', transform(content, absoluteFrom) { return allReplace(content.toString(), defines) }, @@ -68,14 +103,17 @@ module.exports = [ ]}), new CopyPlugin({ patterns: [ { from: './src/icons', to: 'icons/[file]'}, + { from: './src/resources/*.png', to: 'resources/[name][ext]' }, ]}), + new MiniCssExtractPlugin({ + filename: 'lib/style.css' + }), ], }, - */ // Userscript { entry: { - content: './src/content/content.ts' + content: ['./src/content/content.ts', './src/content/GM_fetch/index.js' ] }, module: { rules: [