Bump dependencies

This commit is contained in:
Andras Schmelczer 2026-06-04 17:46:28 +01:00
parent 52ca1b8844
commit 913abb7642
40 changed files with 6200 additions and 19785 deletions

25290
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -22,32 +22,31 @@
}
},
"devDependencies": {
"@types/socket.io-client": "^1.4.34",
"clean-webpack-plugin": "^3.0.0",
"common-config-webpack-plugin": "^2.0.1",
"css-loader": "^1.0.1",
"@plausible-analytics/tracker": "^0.4.5",
"autoprefixer": "^10.5.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^14.0.0",
"css-loader": "^7.1.4",
"file-loader": "^6.2.0",
"gl-matrix": "3.3.0",
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
"html-webpack-inline-svg-plugin": "^2.3.0",
"html-webpack-plugin": "^4.5.0",
"image-config-webpack-plugin": "^2.0.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"html-inline-css-webpack-plugin": "^1.11.2",
"html-webpack-plugin": "^5.6.7",
"mini-css-extract-plugin": "^2.10.2",
"postcss": "^8.5.15",
"postcss-loader": "^8.2.1",
"resize-observer-polyfill": "^1.5.1",
"resolve-url-loader": "^3.1.1",
"sass": "^1.27.0",
"sass-loader": "^8.0.2",
"sdf-2d": "^0.7.2",
"sass": "^1.100.0",
"sass-loader": "^17.0.0",
"sdf-2d": "^0.7.6",
"shared": "file:../shared",
"socket.io-client": "^2.3.1",
"socket.io-msgpack-parser": "^2.0.0",
"source-map-loader": "^1.1.1",
"svg-url-loader": "^6.0.0",
"terser-webpack-plugin": "^4.2.2",
"ts-config-webpack-plugin": "^2.0.0",
"typescript": "^4.0.3",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
"socket.io-client": "^4.8.3",
"socket.io-msgpack-parser": "^3.0.2",
"source-map-loader": "^5.0.0",
"terser-webpack-plugin": "^5.6.1",
"ts-loader": "^9.6.0",
"typescript": "^6.0.3",
"webpack": "^5.107.2",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.4"
}
}

View file

@ -2,3 +2,14 @@ declare module '*.mp3' {
const content: string;
export default content;
}
// Asset imports handled by webpack loaders. TypeScript 5.x+ requires ambient
// declarations for side-effect imports of these, so declare them here.
declare module '*.png';
declare module '*.ico';
declare module '*.svg';
declare module '*.scss';
// webpack's `mode` inlines `process.env.NODE_ENV` via DefinePlugin. Declare just
// that one global so browser code can read it without pulling in all of @types/node.
declare const process: { env: { NODE_ENV?: string } };

View file

@ -68,13 +68,13 @@
<img
title="Enable sounds"
alt="speaker with sound waves"
src="../static/volume.svg"
src="static/volume.svg"
/>
</label>
<label for="enable-music">
<input id="enable-music" type="checkbox" />
<img title="Enable music" alt="music note" src="../static/music.svg" />
<img title="Enable music" alt="music note" src="static/music.svg" />
</label>
<label for="enable-vibration">
@ -82,7 +82,7 @@
<img
title="Enable vibration on mobile"
alt="vibrating mobile"
src="../static/vibrate.svg"
src="static/vibrate.svg"
/>
</label>
@ -90,7 +90,7 @@
title="Exit from current game"
id="logout"
alt="logout"
src="../static/logout.svg"
src="static/logout.svg"
/>
</section>
</div>
@ -101,7 +101,7 @@
id="toggle-settings"
class="icon"
alt="toggle-settings"
src="../static/settings.svg"
src="static/settings.svg"
/>
</div>
@ -109,17 +109,17 @@
class="full-screen-controllers"
id="minimize"
alt="minimize-application"
src="../static/minimize.svg"
src="static/minimize.svg"
/>
<img
class="full-screen-controllers"
id="maximize"
alt="maximize-application"
src="../static/maximize.svg"
src="static/maximize.svg"
/>
<div id="spinner-container">
<img id="spinner" alt="waiting" src="../static/spinner.svg" />
<img id="spinner" alt="waiting" src="static/spinner.svg" />
</div>
</body>
</html>

View file

@ -7,6 +7,7 @@ import {
ProjectileBase,
} from 'shared';
import './main.scss';
import './scripts/analytics';
import '../static/og-image.png';
import '../static/favicons/apple-touch-icon.png';
import '../static/favicons/favicon-16x16.png';
@ -16,9 +17,6 @@ import { LandingPageBackground } from './scripts/landing-page-background';
import { JoinFormHandler } from './scripts/join-form-handler';
import { handleFullScreen } from './scripts/helper/handle-full-screen';
import { Game } from './scripts/game';
import { handleInsights } from './scripts/handle-insights';
import { getInsightsFromRenderer } from './scripts/get-insights-from-renderer';
import { Renderer } from 'sdf-2d';
import ResizeObserver from 'resize-observer-polyfill';
import { OptionsHandler } from './scripts/options-handler';
import { hide } from './scripts/helper/hide';
@ -55,39 +53,6 @@ const enableMusic = document.querySelector('#enable-music') as HTMLInputElement;
const enableVibration = document.querySelector('#enable-vibration') as HTMLInputElement;
const spinner = document.querySelector('#spinner-container') as HTMLElement;
let isInGame = false;
const startInsights = (getRenderer: () => Renderer | undefined) => {
const { vendor, renderer } = getInsightsFromRenderer(getRenderer());
handleInsights(
{
vendor,
renderer,
referrer: document.referrer,
connection: (navigator as any)?.connection?.effectiveType,
devicePixelRatio: devicePixelRatio,
},
() => {
const {
fps,
renderScale,
lightScale,
canvasWidth,
canvasHeight,
} = getInsightsFromRenderer(getRenderer());
return {
isInGame,
fps,
renderScale,
lightScale,
canvasWidth,
canvasHeight,
};
},
);
};
const toggleSettings = () => {
settings.className = settings.className === 'open' ? '' : 'open';
SoundHandler.play(Sounds.click);
@ -157,9 +122,6 @@ const main = async () => {
});
window.onpopstate = () => game.destroy();
let backgroundRenderer: Renderer | undefined;
startInsights(() => (isInGame ? game.renderer : backgroundRenderer));
for (;;) {
show(spinner);
hide(logoutButton, true);
@ -168,7 +130,7 @@ const main = async () => {
const background = new LandingPageBackground(canvas);
const joinHandler = new JoinFormHandler(joinGameForm, serverContainer);
backgroundRenderer = await background.renderer;
await background.renderer;
hide(spinner);
const playerDecision = await joinHandler.getPlayerDecision();
@ -185,11 +147,9 @@ const main = async () => {
game = new Game(playerDecision, canvas, overlay);
const gameOver = game.start();
await game.started;
isInGame = true;
hide(spinner);
show(logoutButton, true, 'block');
await gameOver;
isInGame = false;
}
} catch (e) {
console.error(e);

View file

@ -1,7 +1,8 @@
import { Command, CommandReceiver, serialize, TransportEvents } from 'shared';
import { Socket } from 'socket.io-client';
export class CommandSocket extends CommandReceiver {
constructor(private readonly socket: SocketIOClient.Socket) {
constructor(private readonly socket: Socket) {
super();
}

View file

@ -3,7 +3,10 @@ import { CommandGenerator, PrimaryActionCommand, SecondaryActionCommand } from '
import { Game } from '../game';
export class MouseListener extends CommandGenerator {
constructor(private target: HTMLElement, private readonly game: Game) {
constructor(
private target: HTMLElement,
private readonly game: Game,
) {
super();
target.addEventListener('mousedown', this.mouseDownListener);

View file

@ -22,7 +22,7 @@ import {
Command,
settings,
} from 'shared';
import io from 'socket.io-client';
import { io, Socket } from 'socket.io-client';
import { KeyboardListener } from './commands/keyboard-listener';
import { MouseListener } from './commands/mouse-listener';
import { TouchListener } from './commands/touch-listener';
@ -38,7 +38,7 @@ import { StepCommand } from './commands/types/step';
export class Game extends CommandReceiver {
public gameObjects = new GameObjectContainer(this);
public renderer?: Renderer;
private socket!: SocketIOClient.Socket;
private socket!: Socket;
private isBetweenGames = false;
public started: Promise<void>;
@ -92,7 +92,9 @@ export class Game extends CommandReceiver {
parser,
} as any);
this.socket.on('reconnect_attempt', () => {
// In socket.io-client v4 reconnection events are emitted by the Manager
// (`socket.io`), not the Socket itself.
this.socket.io.on('reconnect_attempt', () => {
this.socket.io.opts.transports = ['polling', 'websocket'];
});

View file

@ -42,7 +42,7 @@ export const handleFullScreen = (
});
addEventListener('resize', () => {
if (isInFullScreen && currentWindowHeight > innerHeight) {
if (isInFullScreen() && currentWindowHeight > innerHeight) {
followToggle();
}
});

View file

@ -1,5 +1,5 @@
import { ServerInformation, serverInformationEndpoint, TransportEvents } from 'shared';
import io from 'socket.io-client';
import { io, Socket } from 'socket.io-client';
import { Configuration } from './configuration';
import parser from 'socket.io-msgpack-parser';
import { SoundHandler, Sounds } from './sound-handler';
@ -21,7 +21,10 @@ export class JoinFormHandler {
}
};
constructor(private form: HTMLFormElement, private readonly container: HTMLElement) {
constructor(
private form: HTMLFormElement,
private readonly container: HTMLElement,
) {
this.joinButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
this.joinButton.disabled = true;
this.waitingForDecision = new Promise((r) => (this.resolvePlayerDecision = r));
@ -32,9 +35,9 @@ export class JoinFormHandler {
form.onsubmit = (e) => {
SoundHandler.play(Sounds.click);
const result: PlayerDecision = (Array.from(
(new FormData(form) as any).entries(),
) as Array<[string, any]>).reduce((result, [name, value]) => {
const result: PlayerDecision = (
Array.from((new FormData(form) as any).entries()) as Array<[string, any]>
).reduce((result, [name, value]) => {
(result as any)[name] = value;
return result;
}, {}) as any;
@ -116,7 +119,7 @@ class ServerChooserOption {
private serverNameElement = document.createElement('span');
private completionElement = document.createElement('span');
private socket: SocketIOClient.Socket;
private socket: Socket;
constructor(
private content: ServerInformation,
@ -145,8 +148,9 @@ class ServerChooserOption {
parser,
} as any);
// `connect_timeout` was removed in socket.io-client v3+; connection
// timeouts now surface through `connect_error` (reconnection is disabled).
this.socket.on('connect_error', this.destroy.bind(this));
this.socket.on('connect_timeout', this.destroy.bind(this));
this.socket.on('disconnect', this.destroy.bind(this));
this.socket.emit(TransportEvents.SubscribeForServerInfoUpdates);
this.socket.on(

View file

@ -15,9 +15,9 @@ export abstract class OptionsHandler {
musicEnabled: true,
};
public static initialize(
inputElements: { [k in Extract<keyof Options, string>]: HTMLInputElement },
) {
public static initialize(inputElements: {
[k in Extract<keyof Options, string>]: HTMLInputElement;
}) {
if (localStorage.getItem('options')) {
const stored: Partial<Options> | null = JSON.parse(
localStorage.getItem('options')!,
@ -44,7 +44,11 @@ export abstract class OptionsHandler {
}
if (k === 'musicEnabled') {
this.checked ? SoundHandler.playAmbient() : SoundHandler.stopAmbient();
if (this.checked) {
SoundHandler.playAmbient();
} else {
SoundHandler.stopAmbient();
}
}
if (this.checked && k === 'vibrationEnabled') {

View file

@ -89,8 +89,8 @@ export class PlanetShape extends PolygonFactory(settings.planetEdgeCount, 0) {
if (dist < minDistance) {
minDistance = dist;
color = mix(${colorToString(settings.declaPlanetColor)}, ${colorToString(
settings.redPlanetColor,
)}, planetColorMixQ[j]);
settings.redPlanetColor,
)}, planetColorMixQ[j]);
}
}
@ -113,7 +113,10 @@ export class PlanetShape extends PolygonFactory(settings.planetEdgeCount, 0) {
public randomOffset = 0;
constructor(public vertices: Array<vec2>, public colorMixQ: number) {
constructor(
public vertices: Array<vec2>,
public colorMixQ: number,
) {
super(vertices);
}

View file

@ -1,10 +1,11 @@
@use 'sass:math';
@use 'vars' as *;
@use 'mixins' as *;
form {
@include background;
padding: $small-padding / 2 $small-padding $small-padding $small-padding;
padding: math.div($small-padding, 2) $small-padding $small-padding $small-padding;
border-radius: $border-radius;
input:-webkit-autofill,
@ -38,7 +39,7 @@ form {
position: relative;
padding: 15px 0 0;
margin-top: 10px;
margin: 10px $small-padding / 2 $small-padding $small-padding / 2;
margin: 10px math.div($small-padding, 2) $small-padding math.div($small-padding, 2);
input {
font-family: inherit;
@ -99,7 +100,7 @@ form {
cursor: pointer;
margin: $border-width-focused - $border-width $border-width-focused -
$border-width + $small-padding / 2;
$border-width + math.div($small-padding, 2);
.completion {
font-size: 0.7em;
@ -115,7 +116,7 @@ form {
&:checked + label {
border-color: $accent;
border-width: $border-width-focused;
margin: 0 $small-padding / 2;
margin: 0 math.div($small-padding, 2);
}
}
}

View file

@ -1,3 +1,4 @@
@use 'sass:math';
@use 'vars' as *;
@use 'mixins' as *;
@ -107,7 +108,7 @@
$height: 4px;
width: $height;
top: $height / 2;
top: math.div($height, 2);
right: 0;
transform-origin: top right;
@ -119,7 +120,7 @@
@media (max-width: $breakpoint) {
$height: 2px;
width: $height;
top: $height / 2;
top: math.div($height, 2);
}
}
@ -152,11 +153,11 @@
label:not(:first-child) {
input[type='checkbox']:after {
$height: 4px;
top: $height / 2 + $small-padding;
top: math.div($height, 2) + $small-padding;
@media (max-width: $breakpoint) {
right: $small-padding;
top: $height / 2;
top: math.div($height, 2);
}
}
}

View file

@ -1,12 +1,13 @@
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"target": "es6",
"esModuleInterop": true,
"strict": true,
"experimentalDecorators": true,
"downlevelIteration": true,
"moduleResolution": "node",
"ignoreDeprecations": "6.0",
"skipLibCheck": true,
"module": "commonjs",
"lib": ["dom", "es2017"],

View file

@ -1,110 +1,148 @@
const CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TsConfigWebpackPlugin = require('ts-config-webpack-plugin');
const HtmlWebpackInlineSVGPlugin = require('html-webpack-inline-svg-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HTMLInlineCSSWebpackPlugin = require('html-inline-css-webpack-plugin').default;
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const Sass = require('sass');
module.exports = (env, argv) => ({
devServer: {
host: '0.0.0.0',
disableHostCheck: true,
module.exports = (env, argv) => {
const isProduction = argv.mode !== 'development';
return {
entry: {
main: path.resolve(__dirname, 'src/index.ts'),
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
host: '0.0.0.0',
allowedHosts: 'all',
},
// webpack-dev-server 5 no longer accepts `watchOptions` under `devServer`;
// polling is configured on the compiler instead (needed for some FS mounts).
watchOptions: {
poll: true,
},
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
//new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
inlineSource: argv.mode === 'development' ? '' : '.(css)$',
}),
new HtmlWebpackInlineSourcePlugin(HtmlWebpackPlugin),
new HtmlWebpackInlineSVGPlugin({
inlineAll: true,
svgoConfig: [
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
//new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
}),
// The SVG UI icons used to be inlined into the HTML by the (abandoned,
// webpack-4-only) html-webpack-inline-svg-plugin. They are now emitted as
// static files and referenced via `static/<name>.svg` from index.html.
new CopyWebpackPlugin({
patterns: [{ from: 'static/*.svg', to: 'static/[name][ext]' }],
}),
// Inline the extracted CSS into the HTML for production builds (replaces
// the abandoned html-webpack-inline-source-plugin). In development the CSS
// stays a separate, linked file for faster rebuilds.
...(isProduction ? [new HTMLInlineCSSWebpackPlugin()] : []),
],
optimization: {
minimizer: [
new TerserJSPlugin({
exclude: /node_modules/,
// The custom serialization protocol keys on class names, so they must
// survive minification (see shared/src/serialization).
terserOptions: {
keep_classnames: true,
},
}),
],
},
module: {
rules: [
{
removeViewBox: false,
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader'],
},
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.scss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
implementation: Sass,
},
},
],
},
{
// SVGs referenced from CSS (`mask-image`/`background-image`) and JS
// must be inlined as real `data:` URIs. svg-url-loader emits a CJS
// module (`module.exports = "data:..."`) which webpack 5 + css-loader 7
// write out verbatim as a `.svg` asset file, so the browser fetches JS
// instead of an image and the mask/background silently fails. webpack 5's
// built-in `asset/inline` produces a proper data URI instead.
// (HTML <img> icons are unaffected: they are copied by CopyWebpackPlugin
// and referenced by literal `static/*.svg` paths, not through this rule.)
test: /\.svg$/,
type: 'asset/inline',
},
{
// Use oneOf so each asset matches exactly one rule (og-image.png would
// otherwise match both the generic png rule and its own rule).
// The directory is encoded in `name` (not `outputPath`) and has no
// leading slash: with the default `publicPath: 'auto'` a leading slash
// produces a doubled slash in the emitted URL (e.g. `//static/x.mp3`).
oneOf: [
{
test: /og-image\.png$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
},
{
test: /\.ico$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
},
{
test: /\.(mp3|png)$/,
use: {
loader: 'file-loader',
options: {
name: 'static/[name].[ext]',
},
},
},
],
},
],
}),
new TsConfigWebpackPlugin(),
],
optimization: {
minimizer: [
new TerserJSPlugin({
test: /\.js$/,
exclude: /node_modules/,
terserOptions: {
keep_classnames: true,
},
}),
],
},
module: {
rules: [
{
test: /\.js$/,
enforce: 'pre',
use: ['source-map-loader'],
},
resolve: {
extensions: ['.ts', '.js', '.json'],
alias: {
// sdf-2d's package.json `exports` only declares an `import` condition,
// which webpack 5 cannot resolve for the production (require) build.
// Point straight at its entry to bypass package exports resolution.
'sdf-2d$': path.resolve(__dirname, 'node_modules/sdf-2d/lib/main.js'),
},
{
test: /\.scss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'sass-loader',
options: {
sourceMap: true,
implementation: Sass,
},
},
],
},
{
test: /\.svg$/,
use: {
loader: 'svg-url-loader',
options: {},
},
},
{
test: /\.(mp3|png)$/,
use: {
loader: 'file-loader',
query: {
outputPath: '/static',
name: '[name].[ext]',
},
},
},
{
test: /\.ico$/,
use: {
loader: 'file-loader',
query: {
outputPath: '/',
name: '[name].[ext]',
},
},
},
{
test: /og-image.png$/,
use: {
loader: 'file-loader',
query: {
outputPath: '/',
name: '[name].[ext]',
},
},
},
],
},
});
},
};
};