jsbeeb 1.19.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/url-params.js +30 -12
package/README.md
CHANGED
|
@@ -51,15 +51,15 @@ KEY.<host key>=<BBC key>
|
|
|
51
51
|
Add one for each key you want to change. For example, Superior Software's Space Invaders fires with `COPY`; this makes
|
|
52
52
|
`Enter` fire instead:
|
|
53
53
|
|
|
54
|
-
[`https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&KEY.ENTER=COPY`](https://bbc.xania.org/?disc1=sth
|
|
54
|
+
[`https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&KEY.ENTER=COPY`](https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&KEY.ENTER=COPY)
|
|
55
55
|
|
|
56
56
|
Superior's Frogger uses `A`/`Z`/`DELETE`/`COPY` to move; this puts it on the arrow keys:
|
|
57
57
|
|
|
58
|
-
[`https://bbc.xania.org/?disc1=sth:Superior/Frogger-Superior.zip&autoboot&KEY.UP=A&KEY.DOWN=Z&KEY.LEFT=DELETE&KEY.RIGHT=COPY`](https://bbc.xania.org/?disc1=sth
|
|
58
|
+
[`https://bbc.xania.org/?disc1=sth:Superior/Frogger-Superior.zip&autoboot&KEY.UP=A&KEY.DOWN=Z&KEY.LEFT=DELETE&KEY.RIGHT=COPY`](https://bbc.xania.org/?disc1=sth:Superior/Frogger-Superior.zip&autoboot&KEY.UP=A&KEY.DOWN=Z&KEY.LEFT=DELETE&KEY.RIGHT=COPY)
|
|
59
59
|
|
|
60
60
|
And Superior's Hunchback steers with `CAPS LOCK` and `CTRL`, which the arrow keys can stand in for:
|
|
61
61
|
|
|
62
|
-
[`https://bbc.xania.org/?disc1=sth:Superior/Hunchback-Superior.zip&autoboot&KEY.LEFT=CAPSLOCK&KEY.RIGHT=CTRL`](https://bbc.xania.org/?disc1=sth
|
|
62
|
+
[`https://bbc.xania.org/?disc1=sth:Superior/Hunchback-Superior.zip&autoboot&KEY.LEFT=CAPSLOCK&KEY.RIGHT=CTRL`](https://bbc.xania.org/?disc1=sth:Superior/Hunchback-Superior.zip&autoboot&KEY.LEFT=CAPSLOCK&KEY.RIGHT=CTRL)
|
|
63
63
|
|
|
64
64
|
The **host key** names are jsbeeb's names for the keys on your own keyboard. Most are what you'd expect, but note:
|
|
65
65
|
|
|
@@ -141,7 +141,7 @@ GP.<gamepad control>=<BBC key>
|
|
|
141
141
|
By default the D-pad presses the "Snapper" keys (`Z`, `X`, `:`, `/`), the `A` button presses `RETURN` and `Start`
|
|
142
142
|
presses `SPACE`. To play Superior's Space Invaders on a pad, where `COPY` fires:
|
|
143
143
|
|
|
144
|
-
[`https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&GP.FIRE=COPY`](https://bbc.xania.org/?disc1=sth
|
|
144
|
+
[`https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&GP.FIRE=COPY`](https://bbc.xania.org/?disc1=sth:Superior/SpaceInvaders-Superior.zip&autoboot&GP.FIRE=COPY)
|
|
145
145
|
|
|
146
146
|
The gamepad control names are:
|
|
147
147
|
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"name": "jsbeeb",
|
|
8
8
|
"description": "Emulate a BBC Micro",
|
|
9
9
|
"repository": "git@github.com:mattgodbolt/jsbeeb.git",
|
|
10
|
-
"version": "1.19.
|
|
10
|
+
"version": "1.19.1",
|
|
11
11
|
"//engines": "If you change the version of Node, it must also be updated at the top of the Dockerfile.",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=24.15.0"
|
package/src/url-params.js
CHANGED
|
@@ -41,18 +41,15 @@ export const ParamTypes = {
|
|
|
41
41
|
export function parseQueryString(queryString, paramTypes = {}) {
|
|
42
42
|
if (!queryString) return {};
|
|
43
43
|
|
|
44
|
-
// workaround for shonky python web server
|
|
45
|
-
const cleanQueryString = queryString.endsWith("/") ? queryString.substring(0, queryString.length - 1) : queryString;
|
|
46
|
-
|
|
47
44
|
const parsedQuery = {};
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
queryString.split("&").forEach(function (keyval) {
|
|
50
47
|
if (!keyval) return;
|
|
51
48
|
|
|
52
49
|
const keyAndVal = keyval.split("=");
|
|
53
50
|
const key = decodeURIComponent(keyAndVal[0]);
|
|
54
51
|
let val = null;
|
|
55
|
-
if (keyAndVal.length > 1) val = decodeURIComponent(keyAndVal
|
|
52
|
+
if (keyAndVal.length > 1) val = decodeURIComponent(keyAndVal.slice(1).join("="));
|
|
56
53
|
|
|
57
54
|
const paramType = paramTypes[key] || ParamTypes.STRING;
|
|
58
55
|
|
|
@@ -90,12 +87,26 @@ export function parseQueryString(queryString, paramTypes = {}) {
|
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* @param {Object.<string, ParamType>} [paramTypes={}] - Object mapping parameter names to their types
|
|
97
|
-
* @returns {string} The complete URL with query parameters
|
|
90
|
+
* Characters RFC 3986 allows literally in a query that `encodeURIComponent` escapes anyway. `&`,
|
|
91
|
+
* `=`, `+`, `#`, `%` and space are left escaped because they delimit something, and `?` because a
|
|
92
|
+
* URL with a second one in it reads as broken even though the grammar permits it.
|
|
98
93
|
*/
|
|
94
|
+
const QueryLiterals = "$,/:;@";
|
|
95
|
+
|
|
96
|
+
const EscapedQueryLiterals = new RegExp(
|
|
97
|
+
[...QueryLiterals].map((char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`).join("|"),
|
|
98
|
+
"g",
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Percent-encode a key or value for a query string, escaping only what delimits something
|
|
103
|
+
* @param {string} component - The key or value to encode
|
|
104
|
+
* @returns {string} The encoded component
|
|
105
|
+
*/
|
|
106
|
+
function encodeQueryComponent(component) {
|
|
107
|
+
return encodeURIComponent(component).replace(EscapedQueryLiterals, decodeURIComponent);
|
|
108
|
+
}
|
|
109
|
+
|
|
99
110
|
/**
|
|
100
111
|
* Append a parameter to the URL
|
|
101
112
|
* @param {string} url - Current URL
|
|
@@ -105,13 +116,20 @@ export function parseQueryString(queryString, paramTypes = {}) {
|
|
|
105
116
|
* @returns {Object} Updated URL and separator
|
|
106
117
|
*/
|
|
107
118
|
function appendParam(url, sep, key, value = undefined) {
|
|
108
|
-
url += sep +
|
|
119
|
+
url += sep + encodeQueryComponent(key);
|
|
109
120
|
if (value !== undefined) {
|
|
110
|
-
url += "=" +
|
|
121
|
+
url += "=" + encodeQueryComponent(value);
|
|
111
122
|
}
|
|
112
123
|
return { url, sep: "&" };
|
|
113
124
|
}
|
|
114
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Build a URL string from base URL and query parameters
|
|
128
|
+
* @param {string} baseUrl - The base URL (without query string)
|
|
129
|
+
* @param {Object} parsedQuery - Object containing query parameters
|
|
130
|
+
* @param {Object.<string, ParamType>} [paramTypes={}] - Object mapping parameter names to their types
|
|
131
|
+
* @returns {string} The complete URL with query parameters
|
|
132
|
+
*/
|
|
115
133
|
export function buildUrlFromParams(baseUrl, parsedQuery, paramTypes = {}) {
|
|
116
134
|
let url = baseUrl;
|
|
117
135
|
let sep = "?";
|