<feed xmlns='http://www.w3.org/2005/Atom'>
<title>webao/webAO, branch master</title>
<subtitle>WebAO fork</subtitle>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/'/>
<entry>
<title>Add passkey authentication (WebAuthn)</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-07T13:19:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=aa4c30bb6d1e46b5019065fba6c0eb3c08aa1f34'/>
<id>aa4c30bb6d1e46b5019065fba6c0eb3c08aa1f34</id>
<content type='text'>
Bring in the subprotocol (the same as what's used on the desktop client
for public-key authentication) to carry the relevant messages:

- AuthRequest: first step in the flow, the client sends it to signal the
  intent to authenticate to the server.

- AssertCredential and AssertionFinish: server's challenge and client's
  response, respectively, to finalize the flow.

- RegisterCredential and RegistrationFinish: same structure as the
  above. Unlike the simple public-key auth with an out-of-band setup,
  passkeys require user interaction to register. User must be
  authorized.

Validate all relevant checks on the API side, and hand the data over to
the server for it to verify attestations and assertions.

Because it's a primary auth mechanism (not a second factor), require
user verification.

As we don't use any other method on web, add a passkey button as the
only sign-in interface. Passkeys are discoverable, we don't even need a
username.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Bring in the subprotocol (the same as what's used on the desktop client
for public-key authentication) to carry the relevant messages:

- AuthRequest: first step in the flow, the client sends it to signal the
  intent to authenticate to the server.

- AssertCredential and AssertionFinish: server's challenge and client's
  response, respectively, to finalize the flow.

- RegisterCredential and RegistrationFinish: same structure as the
  above. Unlike the simple public-key auth with an out-of-band setup,
  passkeys require user interaction to register. User must be
  authorized.

Validate all relevant checks on the API side, and hand the data over to
the server for it to verify attestations and assertions.

Because it's a primary auth mechanism (not a second factor), require
user verification.

As we don't use any other method on web, add a passkey button as the
only sign-in interface. Passkeys are discoverable, we don't even need a
username.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove defunct CAPTCHA</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-07T03:16:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=ae7ef2c6c76947ea12cbb1592152d9c80fd1a8f3'/>
<id>ae7ef2c6c76947ea12cbb1592152d9c80fd1a8f3</id>
<content type='text'>
The hCaptcha integration has been abandoned for a while. It added yet
another questionable third-party API (which also set a Cloudflare
cookie), and its effectiveness is unclear considering its client-side
nature.

A custom CAPTCHA implementation (such as PoW challenge) is an
interesting prospect, but it'll require proper server-side support.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The hCaptcha integration has been abandoned for a while. It added yet
another questionable third-party API (which also set a Cloudflare
cookie), and its effectiveness is unclear considering its client-side
nature.

A custom CAPTCHA implementation (such as PoW challenge) is an
interesting prospect, but it'll require proper server-side support.
</pre>
</div>
</content>
</entry>
<entry>
<title>Replace cookies with localStorage</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-07T02:55:26+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=4bd750ca1f3e446f68e0f88fabf0682fd4d61848'/>
<id>4bd750ca1f3e446f68e0f88fabf0682fd4d61848</id>
<content type='text'>
Cookies's use case is to store persistent data and send it to the server
in subsequent requests, such as to remember logged-in sessions. WebAO is
using them to store site settings like ad-hoc hash tables that require
parsing and serialization.

As a nasty side-effect of how cookies work, clients send all their
settings every time they connect to the server. Server has absolutely no
use for them, but each client sends them anyway, which is an
uncalled-for privacy leak.

Remove this mechanism entirely, switch to localStorage which serves
exactly the purpose of per-origin store with data that never leaves the
browser.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Cookies's use case is to store persistent data and send it to the server
in subsequent requests, such as to remember logged-in sessions. WebAO is
using them to store site settings like ad-hoc hash tables that require
parsing and serialization.

As a nasty side-effect of how cookies work, clients send all their
settings every time they connect to the server. Server has absolutely no
use for them, but each client sends them anyway, which is an
uncalled-for privacy leak.

Remove this mechanism entirely, switch to localStorage which serves
exactly the purpose of per-origin store with data that never leaves the
browser.
</pre>
</div>
</content>
</entry>
<entry>
<title>Update dependencies and ECMAScript target</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-06T14:48:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=085204dbdf17f379c9a32ea11660accb51b4311d'/>
<id>085204dbdf17f379c9a32ea11660accb51b4311d</id>
<content type='text'>
Fix relevant breaking changes.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Fix relevant breaking changes.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove attorneyMarkdown</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-06T22:06:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=3e5eda1fd61ab057472c4e8734ba57e1a0c84a33'/>
<id>3e5eda1fd61ab057472c4e8734ba57e1a0c84a33</id>
<content type='text'>
It only caused chat_config.ini to be requested a lot.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
It only caused chat_config.ini to be requested a lot.
</pre>
</div>
</content>
</entry>
<entry>
<title>Change extension of positions to WebP</title>
<updated>2026-04-18T16:52:23+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-06T22:06:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=6a7a4f68711432d73fbf1355f15402fa6d31aafa'/>
<id>6a7a4f68711432d73fbf1355f15402fa6d31aafa</id>
<content type='text'>
These should obey the extension data, but instead they're hardcoded
to predefined PNG and GIF files.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
These should obey the extension data, but instead they're hardcoded
to predefined PNG and GIF files.
</pre>
</div>
</content>
</entry>
<entry>
<title>Temporarily default to blips value "m"</title>
<updated>2026-04-18T16:52:22+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-06T22:05:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=1c458e5841ae30bed8b6f3107d3f65353d9b731c'/>
<id>1c458e5841ae30bed8b6f3107d3f65353d9b731c</id>
<content type='text'>
Blips aren't handled correctly every time, resulting in a lot of 404
URLs and invalid blips.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Blips aren't handled correctly every time, resulting in a lot of 404
URLs and invalid blips.
</pre>
</div>
</content>
</entry>
<entry>
<title>Guess WSS, not WS, if the schema isn't set</title>
<updated>2026-04-18T16:52:22+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-03-31T14:23:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=14b8190d1e0efdfd7b2650923f7b014614dcf937'/>
<id>14b8190d1e0efdfd7b2650923f7b014614dcf937</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Separate the MC packet into music and area change</title>
<updated>2026-04-18T16:52:22+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-03-16T16:33:54+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=3f1140da7779f568137d62b3f35392edc9e02e1e'/>
<id>3f1140da7779f568137d62b3f35392edc9e02e1e</id>
<content type='text'>
Historically, MC packet ended up in a ridiculous spot. It had this
single structure:

MC#something#cid#%

It used to change music track to `something`, and the character ID `cid`
was used in clientside muting (blindly trusted, by the way). Then,
this packet was expanded to mean area change as well, so the same
generic structure carried two completely different meanings.

How does one differentiate the two? Whether the client tried to move to
an area `something`, or played a music track called `something`?

The solution was to assume that having ".extension" at the end magically
implied that it was a name of a music file, check the string `something`
within the MC packet, and pray that you guessed correctly. So,
understanding the protocol message required penetrating into one of its
data fields and ambiguously inferring what the whole message even meant.

Modern AO gives us a more logical solution. Not as good as having two
separate packets for two unrelated actions, but we can at least discern
the area and music change directly from the framing.

Area change uses the same two-field structure: MC#area#cid#%

Music change, however, has acquired two additional fields:
MC#music#cid#showname#flags#%

We consider four-field MC to be music change, and two-field MC to be
area change, resolving the ambiguity and eliminating odd constraints
on area and music names.

WebAO still uses the old logic and sends two-field MC packets for both
cases. CSDWASASH server, as a result, thinks that web users try to
change areas when they play music. This commit fixes this behavior and
adds special sendAreaChange instead of using sendMusicChange for both.
The flags are hardcoded to 0 because WebAO can't set fade-in, fade-out,
or position sync, and it ignores the server flags.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Historically, MC packet ended up in a ridiculous spot. It had this
single structure:

MC#something#cid#%

It used to change music track to `something`, and the character ID `cid`
was used in clientside muting (blindly trusted, by the way). Then,
this packet was expanded to mean area change as well, so the same
generic structure carried two completely different meanings.

How does one differentiate the two? Whether the client tried to move to
an area `something`, or played a music track called `something`?

The solution was to assume that having ".extension" at the end magically
implied that it was a name of a music file, check the string `something`
within the MC packet, and pray that you guessed correctly. So,
understanding the protocol message required penetrating into one of its
data fields and ambiguously inferring what the whole message even meant.

Modern AO gives us a more logical solution. Not as good as having two
separate packets for two unrelated actions, but we can at least discern
the area and music change directly from the framing.

Area change uses the same two-field structure: MC#area#cid#%

Music change, however, has acquired two additional fields:
MC#music#cid#showname#flags#%

We consider four-field MC to be music change, and two-field MC to be
area change, resolving the ambiguity and eliminating odd constraints
on area and music names.

WebAO still uses the old logic and sends two-field MC packets for both
cases. CSDWASASH server, as a result, thinks that web users try to
change areas when they play music. This commit fixes this behavior and
adds special sendAreaChange instead of using sendMusicChange for both.
The flags are hardcoded to 0 because WebAO can't set fade-in, fade-out,
or position sync, and it ignores the server flags.
</pre>
</div>
</content>
</entry>
<entry>
<title>Change image extension priority</title>
<updated>2026-04-18T16:52:22+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-03-16T16:19:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=29571c0da3b3a588b57125e5dc56eaa78639c1b7'/>
<id>29571c0da3b3a588b57125e5dc56eaa78639c1b7</id>
<content type='text'>
Sometimes, WebP icons won't load despite extensions.json clearly
defining it as the only extension used for all image data.

I suspect there's a race condition between fetching extensions.json,
parsing it into client, and checking what extension we should use to get
character icons during loading. Sometimes it correctly loads images,
sometimes it falls back and starts requesting PNG instead.

I couldn't precisely identify where it happens and what's the root
cause. As a workaround, this commit instead makes WebP the
first-priority extension and a fallback.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Sometimes, WebP icons won't load despite extensions.json clearly
defining it as the only extension used for all image data.

I suspect there's a race condition between fetching extensions.json,
parsing it into client, and checking what extension we should use to get
character icons during loading. Sometimes it correctly loads images,
sometimes it falls back and starts requesting PNG instead.

I couldn't precisely identify where it happens and what's the root
cause. As a workaround, this commit instead makes WebP the
first-priority extension and a fallback.
</pre>
</div>
</content>
</entry>
</feed>
