A rewrite of Poly+, my quality-of-life browser extension for Polytoria. Built entirely fresh using the WXT extension framework, Typescript, and with added better overall code quality.
extension

feat: IRL brick price on Resellers

Index ce330458 72f7874a

Changed files
+65 -14
entrypoints
utils
+4 -2
entrypoints/preferences-handler.ts
···
-
import { preferences, defaultPreferences, preferencesSchema, cache } from "@/utils/storage";
+
import { preferences, defaultPreferences, preferencesSchema } from "@/utils/storage";
+
import config from "@/utils/config.json";
import data from "@/public/preferences.json";
declare global {
···
values = preferenceValues;
window.polyplus = {
preferences: preferenceValues,
-
static: data
+
static: data,
+
config: config
};
console.log('Loaded preferences: ', preferenceValues);
+2 -1
entrypoints/sitewide.content.ts
···
+
import config from "@/utils/config.json";
import { preferences } from "@/utils/storage";
import { getUserDetails, bricksToCurrency } from "@/utils/utilities";
import plusIcon from "/svgs/plus.svg";
···
return;
};
-
console.info('[Poly+] Logged in as: ', user);
+
if (config.devBuild) console.info('[Poly+] Logged in as: ', user);
preferences.getPreferences()
.then((values) => {
+5
entrypoints/store.content/discovery.ts
···
300000,
false
);
+
+
if (inventory == "disabled") {
+
console.error('[Poly+] API is disabled, cancelling item owned tags loading..');
+
return;
+
};
const owns = (item: HTMLElement) => {
const itemId = parseInt(item.getAttribute('href')!.split('/')[2]);
+7 -1
entrypoints/store.content/index.ts
···
import { getUserDetails } from "@/utils/utilities";
import { preferences } from "@/utils/storage";
+
import config from "@/utils/config.json";
import * as discovery from "./discovery";
import * as view from "./view";
+
import * as apiTypes from "@/utils/types";
export default defineContentScript({
matches: ['*://polytoria.com/store/*'],
···
if (!window.location.pathname.split('/')[2]) {
// Discovery
+
if (config.devBuild) console.log('[Poly+] Running discovery page functions: ', discovery);
+
if (values.irlBrickPrice.enabled) discovery.irlBrickPrice();
if (values.storeOwnedTags.enabled) discovery.ownedTags(user.userId);
} else {
// View
+
if (config.devBuild) console.log('[Poly+] Running view page functions: ', discovery);
+
if (values.irlBrickPrice.enabled) view.irlBrickPrice();
-
if (values.tryItems.enabled) view.tryOn(user);
+
if (values.tryItems.enabled) view.tryOn(user as apiTypes.userDetails);
};
})
});
+35 -2
entrypoints/store.content/view.ts
···
// only happen when the item is already owned
console.warn('[Poly+] Failure to find purchase button on page.');
};
+
+
const resellers = document.getElementById('resellers-container');
+
if (resellers) {
+
const mutations = new MutationObserver((mutations) => {
+
for (const record of mutations) {
+
for (const node of record.addedNodes) {
+
const price = (node as HTMLElement).getElementsByClassName('text-success')[0];
+
const purchaseBtn = (node as HTMLElement).querySelector('button[data-listing-id]');
+
+
if (price && purchaseBtn) {
+
const currency = bricksToCurrency(parseInt(purchaseBtn.getAttribute('data-price')!), "USD");
+
+
if (currency) {
+
const spanTag = document.createElement('span');
+
spanTag.classList.add('text-muted');
+
spanTag.style.fontSize = '0.7rem';
+
spanTag.style.fontWeight = 'lighter';
+
spanTag.innerText = ` (${currency})`;
+
price.appendChild(spanTag);
+
};
+
};
+
};
+
};
+
});
+
+
mutations.observe(resellers, { childList: true });
+
}
};
export function tryOn(user: userDetails) {
···
const button = document.createElement('button');
button.classList.add('btn', 'btn-outline-primary', 'btn-sm', 'mt-2');
+
button.style.width = '50px';
button.innerHTML = '<img src="' + browser.runtime.getURL("/svgs/vial.svg") + '" width="15" height="15">';
const modal = document.createElement('dialog');
modal.classList.add('polyplus-modal');
Object.assign(modal.style, {
width: '450px',
+
height: '350px',
border: '1px solid #484848',
backgroundColor: '#181818',
borderRadius: '20px',
···
modal.innerHTML = `
<div class="row text-muted mb-2" style="font-size: 0.8rem;">
<div class="col">
-
<h5 class="mb-0" style="color: #fff;">Preview</h5>
+
<h5 class="mb-0" style="color: #fff;">Poly+ Item Preview</h5>
Try this item on your avatar before purchasing it!
</div>
<div class="col-md-2">
···
if (avatar == "disabled") {
console.error('[Poly+] API is disabled, cancelling try-on items loading..');
-
modal.getElementsByClassName('modal-body')[0].innerHTML = `
+
const modalBody = modal.getElementsByClassName('modal-body')[0] as HTMLDivElement;
+
modalBody.style.marginTop = '50px';
+
+
modalBody.innerHTML = `
<div class="text-center p-2">
<img src="${ browser.runtime.getURL('/svgs/error.svg') }" width="100" height="100">
<p class="text-muted mb-0">Sorry! This feature is currently unavailable. Please check back later!</p>
···
`;
modal.showModal();
+
button.innerHTML = '<img src="' + browser.runtime.getURL("/svgs/vial.svg") + '" width="15" height="15">';
return;
};
+12 -8
utils/utilities.ts
···
!cacheStorage[key] || !metadata[key] || forceReplenish ||
overlap >= expiry
) {
-
console.info(
-
`[Poly+] "${key}" cache is stale replenishing...`,
-
timeAgo(overlap),
-
);
+
if (config.devBuild) {
+
console.info(
+
`[Poly+] "${key}" cache is stale replenishing...`,
+
timeAgo(overlap),
+
);
+
};
const replenishedCache = await replenish();
···
const overlap = Date.now() - (metadata[store][key] || 0);
if (!cacheStorage[store][key] || forceReplenish || overlap >= expiry) {
-
console.info(
-
`[Poly+] "${key}" KV cache is stale replenishing...`,
-
timeAgo(overlap),
-
);
+
if (config.devBuild) {
+
console.info(
+
`[Poly+] "${key}" KV cache is stale replenishing...`,
+
timeAgo(overlap),
+
);
+
};
const replenishedCache = await replenish();