Skip to content

Commit

Permalink
feat: render room decorations (#32)
Browse files Browse the repository at this point in the history
### Changes
- Proxy requests to S3 resources to prevent CORS errors.
- Remove AWS host from resource URLs so the proxy will handle the
request instead.
- Remove forum RSS feed fetch to prevent console errors.
- Update `getData()` in `app2/main.js` to fetch the version from the
correct API path, preventing occasional console errors.
- Changes the cache control header expiration from 1 year to 1 week, to
help stay updated with steamless client changes.
  • Loading branch information
admon84 authored Aug 11, 2024
1 parent 6f4e306 commit 643223d
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 21 deletions.
41 changes: 29 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"chalk": "^5.3.0",
"ejs": "^3.1.10",
"http-proxy": "1.18.1",
"http-proxy-middleware": "^3.0.0",
"js-beautify": "1.15.1",
"jszip": "^3.10.1",
"koa": "2.15.3",
Expand Down
38 changes: 33 additions & 5 deletions src/clientApp.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import httpProxy from 'http-proxy';
import { createProxyMiddleware } from 'http-proxy-middleware';
import Koa from 'koa';
import koaConditionalGet from 'koa-conditional-get';
import views from '@ladjs/koa-views';
Expand All @@ -23,7 +24,7 @@ import {
} from './utils/utils';
import { logError, handleProxyError, handleServerError } from './utils/errors';
import { clientAuth } from './inject/clientAuth';
import { removeDecorations } from './inject/removeDecorations';
import { roomDecorations } from './inject/roomDecorations';
import { customMenuLinks } from './inject/customMenuLinks';

// Get the app directory and version
Expand All @@ -36,6 +37,7 @@ const version = packageJson.version || '1.0.0';
const arrow = '\u2192';
const localhost = 'localhost';
const defaultPort = 8080;
const awsHost = 'https://s3.amazonaws.com';

// Parse program arguments
const argv = (() => {
Expand Down Expand Up @@ -97,6 +99,7 @@ console.log('🧩', chalk.yellowBright(`Screepers Steamless Client v${version}`)
// Create proxy
const proxy = httpProxy.createProxyServer({ changeOrigin: true });
proxy.on('error', (err, req, res) => handleProxyError(err, res, argv.debug));
const awsProxy = createProxyMiddleware({ target: awsHost, changeOrigin: true });

const exitOnPackageError = () => {
if (argv.package) {
Expand Down Expand Up @@ -142,6 +145,15 @@ koa.use(views(path.join(__dirname, '../views'), { extension: 'ejs' }));
// Serve client assets directly from steam package
koa.use(koaConditionalGet());

// Proxy requests to AWS host to avoid CORS issues
koa.use(async (ctx, next) => {
if (ctx.url.startsWith('/static.screeps.com')) {
await awsProxy(ctx.req, ctx.res, next);
ctx.respond = false;
}
return next();
});

// Render the index.ejs file and pass the serverList variable
koa.use(async (context, next) => {
if (['/', 'index.html'].includes(context.path)) {
Expand Down Expand Up @@ -220,7 +232,7 @@ koa.use(async (context, next) => {
const replaceHeader = [
header,
generateScriptTag(clientAuth, { backend: info.backend, guest: argv.guest }),
generateScriptTag(removeDecorations, { backend: info.backend }),
generateScriptTag(roomDecorations, { backend: info.backend, awsHost }),
generateScriptTag(customMenuLinks, { backend: info.backend, seasonLink, ptrLink, changeServerLink }),
].join('\n');
src = src.replace(header, replaceHeader);
Expand Down Expand Up @@ -277,7 +289,23 @@ koa.use(async (context, next) => {
return src;
} else if (context.path.endsWith('.js')) {
let src = await file.async('text');
if (urlPath === 'build.min.js') {

if (urlPath.startsWith('app2/main.')) {
// Modify getData() to fetch from the correct API path
src = src.replace(/fetch\(t\+"version"\)/g, 'fetch(window.CONFIG.API_URL+"version")');
// Remove fetch to forum RSS feed
src = src.replace(/fetch\("https:\/\/screeps\.com\/forum\/.+\.rss"\)/g, 'Promise.resolve()');
// Remove AWS host from rewards URL
src = src.replace(/https:\/\/s3\.amazonaws\.com/g, '');
} else if (urlPath.startsWith('vendor/renderer/renderer.js')) {
// Modify renderer to remove AWS host from loadElement()
src = src.replace(
/\(this\.data\.src=this\.url\)/g,
`(this.data.src=this.url.replace("${awsHost}",""))`,
);
// Remove AWS host from image URLs
src = src.replace(/src=t,/g, `src=t.replace("${awsHost}",""),`);
} else if (urlPath === 'build.min.js') {
// Load backend info from underlying server
const backend = new URL(info.backend);
const isOfficialLike = isOfficial || (await isOfficialLikeVersion(client));
Expand Down Expand Up @@ -334,9 +362,9 @@ koa.use(async (context, next) => {
const extension = (/\.[^.]+$/.exec(urlPath.toLowerCase())?.[0] ?? '.html') as keyof typeof mimeTypes;
context.set('Content-Type', mimeTypes[extension] ?? 'text/html');

// We can safely cache explicitly-versioned resources forever
// Set cache for resources that change occasionally
if (context.request.query.bust) {
context.set('Cache-Control', 'public,max-age=31536000,immutable');
context.set('Cache-Control', 'public, max-age=604800, immutable'); // Cache for 1 week
}
});

Expand Down
27 changes: 23 additions & 4 deletions src/inject/removeDecorations.ts → src/inject/roomDecorations.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
type AnyObject = Record<PropertyKey, unknown>;
type AnyProps = string | AnyObject | AnyProps[];

/**
* This function is injected into the client to remove room decorations (and avoid CORS errors).
* This function is injected into the client to modify room decorations.
*/
export function removeDecorations(backend: string) {
export function roomDecorations(backend: string, awsHost: string) {
if (!backend.includes('screeps.com')) {
return;
}

// Recursive function to remove AWS host from room decoration URLs
const removeAWSHost = (obj: AnyProps): AnyProps => {
if (typeof obj === 'string') {
return obj.replace(awsHost, '');
} else if (Array.isArray(obj)) {
return obj.map(removeAWSHost) as AnyProps;
} else if (obj && typeof obj === 'object') {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
obj[key] = removeAWSHost(obj[key] as AnyProps);
}
}
}
return obj;
};

const onRoomUpdate = () => {
const roomInterval = setInterval(() => {
const roomElement = document.querySelector('.room.ng-scope');
Expand All @@ -14,8 +33,8 @@ export function removeDecorations(backend: string) {
const connection = window.angular.element(document.body).injector().get('Connection');
const roomScope = window.angular.element(roomElement).scope();
connection.onRoomUpdate(roomScope, () => {
// Remove room decorations
roomScope.Room.decorations = [];
// Modify room decorations to avoid CORS errors
roomScope.Room.decorations = removeAWSHost(roomScope.Room.decorations);
});
}
}, 100);
Expand Down

0 comments on commit 643223d

Please sign in to comment.