dewdrop-sdk 0.5.2 → 0.5.4
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/package.json +1 -1
- package/src/backend.js +4 -4
- package/src/evaluate.js +57 -0
- package/src/net.js +3 -0
- package/src/serve.js +4 -4
- package/src/vite.js +9 -6
package/package.json
CHANGED
package/src/backend.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// past its cap. The page's URL says which room the tab joins and under
|
|
8
8
|
// what name; on Dewdrop the game's page says which room, and the name is
|
|
9
9
|
// the player's username.
|
|
10
|
-
import { MAX_NET_CLIENTS, MAX_NET_CLIENT_FRAME_BYTES, createInbox, createOutbox, defineHandlers, label, side, unpack } from './net.js';
|
|
10
|
+
import { DEFAULT_ROOM_PLAYERS, MAX_NET_CLIENTS, MAX_NET_CLIENT_FRAME_BYTES, createInbox, createOutbox, defineHandlers, label, side, unpack } from './net.js';
|
|
11
11
|
import { NO_CHANNEL } from './route.js';
|
|
12
12
|
|
|
13
13
|
let channel = null;
|
|
@@ -59,9 +59,9 @@ function name(text) {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// The players a room seats as `?players=` names them: a whole number from
|
|
62
|
-
// 1 to MAX_NET_CLIENTS, or
|
|
62
|
+
// 1 to MAX_NET_CLIENTS, or DEFAULT_ROOM_PLAYERS without one or with any other.
|
|
63
63
|
function players(text) {
|
|
64
|
-
return /^[0-9]{1,3}$/.test(text) && Number(text) >= 1 && Number(text) <= MAX_NET_CLIENTS ? Number(text) :
|
|
64
|
+
return /^[0-9]{1,3}$/.test(text) && Number(text) >= 1 && Number(text) <= MAX_NET_CLIENTS ? Number(text) : DEFAULT_ROOM_PLAYERS;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Where a page's URL joins its tab: the room `?room=` names, the name
|
|
@@ -86,7 +86,7 @@ export function gameHasBackend() {
|
|
|
86
86
|
// function at the next animation frame. Without a plugin there is no
|
|
87
87
|
// channel, and `send` says so. A room that seats no more says so in the
|
|
88
88
|
// console, as Dewdrop refuses the connect, and the tab's sends reach no one.
|
|
89
|
-
export function attachBackend(hot, { channel: routed, hasBackend: present, frame: schedule, room = '', player = '', players =
|
|
89
|
+
export function attachBackend(hot, { channel: routed, hasBackend: present, frame: schedule, room = '', player = '', players = DEFAULT_ROOM_PLAYERS }) {
|
|
90
90
|
if (!routed) return;
|
|
91
91
|
channel = hot;
|
|
92
92
|
hasBackend = present;
|
package/src/evaluate.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// A page the dev server serves offers `dewdrop.evaluate(code)`, as a dev
|
|
2
|
+
// version's page on Dewdrop does, so one script drives the game in both.
|
|
3
|
+
// `code`, a string or a function sent as its source text called with
|
|
4
|
+
// nothing, runs at the page's top level, as the browser's console runs
|
|
5
|
+
// what is typed into it: what the game puts on `globalThis` is in reach and
|
|
6
|
+
// its modules' own bindings are not. The call resolves with the value the
|
|
7
|
+
// code came to as JSON carries it, a promise's once it settles, `undefined`
|
|
8
|
+
// for none, or rejects with an Error carrying the text of what the code
|
|
9
|
+
// threw; code, waiting calls and values past what Dewdrop takes are
|
|
10
|
+
// refused in Dewdrop's words. The plugin puts `evaluateScript` in the
|
|
11
|
+
// page's head; it is `offerEvaluate`'s own text, so the function imports
|
|
12
|
+
// nothing and is written without a `<`.
|
|
13
|
+
|
|
14
|
+
// What Dewdrop takes: the bytes of a call's code, the calls waiting at
|
|
15
|
+
// once, and the bytes of a value's JSON.
|
|
16
|
+
export const LIMITS = Object.freeze({ code: 32764, waiting: 16, value: 65530 });
|
|
17
|
+
|
|
18
|
+
// Offers `dewdrop.evaluate` on `scope`, the page's window, within `limits`.
|
|
19
|
+
// Assigned rather than defined, since the game shares the window here.
|
|
20
|
+
export function offerEvaluate(scope, limits) {
|
|
21
|
+
const run = scope.eval;
|
|
22
|
+
const encoder = new scope.TextEncoder();
|
|
23
|
+
const describe = (error) => {
|
|
24
|
+
try {
|
|
25
|
+
return error !== null && typeof error === 'object' && typeof error.stack === 'string' ? error.stack : String(error);
|
|
26
|
+
} catch {
|
|
27
|
+
return '[unprintable]';
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
let waiting = 0;
|
|
31
|
+
async function evaluate(code) {
|
|
32
|
+
if (typeof code === 'function') code = `(${code})()`;
|
|
33
|
+
else if (typeof code !== 'string') throw new TypeError('dewdrop.evaluate takes a string or a function');
|
|
34
|
+
if (waiting >= limits.waiting) throw new RangeError(`dewdrop.evaluate has ${limits.waiting} calls waiting already`);
|
|
35
|
+
const bytes = encoder.encode(code).length;
|
|
36
|
+
if (bytes > limits.code) throw new RangeError(`dewdrop.evaluate takes code of at most ${limits.code} bytes, not ${bytes}`);
|
|
37
|
+
waiting += 1;
|
|
38
|
+
let text;
|
|
39
|
+
try {
|
|
40
|
+
text = JSON.stringify(await run(code));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(describe(error));
|
|
43
|
+
} finally {
|
|
44
|
+
waiting -= 1;
|
|
45
|
+
}
|
|
46
|
+
if (text === undefined) return undefined;
|
|
47
|
+
const length = encoder.encode(text).length;
|
|
48
|
+
if (length > limits.value) throw new Error(`the value is ${length} bytes as JSON, more than the ${limits.value} an answer carries`);
|
|
49
|
+
return JSON.parse(text);
|
|
50
|
+
}
|
|
51
|
+
scope.dewdrop = Object.freeze({ evaluate });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The script the plugin puts in a page the dev server serves.
|
|
55
|
+
export function evaluateScript() {
|
|
56
|
+
return `(${offerEvaluate})(window, ${JSON.stringify(LIMITS)});`;
|
|
57
|
+
}
|
package/src/net.js
CHANGED
|
@@ -18,6 +18,9 @@ export const MAX_NET_PLAYER_BYTES = 64;
|
|
|
18
18
|
// The most players a room seats, and the most clients it holds: a room's
|
|
19
19
|
// size is 1 to this.
|
|
20
20
|
export const MAX_NET_CLIENTS = 100;
|
|
21
|
+
// The players a room seats when its page names no size: the most a game's
|
|
22
|
+
// room seats on Dewdrop until its developer says otherwise.
|
|
23
|
+
export const DEFAULT_ROOM_PLAYERS = 8;
|
|
21
24
|
// How often the local backend ticks: sixty times a second.
|
|
22
25
|
export const NET_TICK_MS = 1000 / 60;
|
|
23
26
|
|
package/src/serve.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { existsSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { dirname, resolve } from 'node:path';
|
|
19
19
|
import { Worker } from 'node:worker_threads';
|
|
20
|
-
import { MAX_NET_CLIENTS, label } from './net.js';
|
|
20
|
+
import { DEFAULT_ROOM_PLAYERS, MAX_NET_CLIENTS, label } from './net.js';
|
|
21
21
|
|
|
22
22
|
// How long a room's backend runs on after its last tab's leave, as on
|
|
23
23
|
// Dewdrop.
|
|
@@ -174,13 +174,13 @@ export function createBackendHost({ entry, ws, watcher, origin = () => '', log =
|
|
|
174
174
|
// A tab joins the room its page named under the name it gave, a room
|
|
175
175
|
// it makes seating the players its page said; a page that named
|
|
176
176
|
// neither joins the empty room unnamed, and one that named no size a
|
|
177
|
-
// room seating
|
|
178
|
-
join(client, { room = '', player = '', players =
|
|
177
|
+
// room seating DEFAULT_ROOM_PLAYERS, as a game's rooms on Dewdrop do.
|
|
178
|
+
join(client, { room = '', player = '', players = DEFAULT_ROOM_PLAYERS } = {}) {
|
|
179
179
|
if (tabs.has(client)) return;
|
|
180
180
|
const key = typeof room === 'string' ? room : '';
|
|
181
181
|
let it = rooms.get(key);
|
|
182
182
|
if (it === undefined) {
|
|
183
|
-
const capacity = Number.isInteger(players) && players >= 1 && players <= MAX_NET_CLIENTS ? players :
|
|
183
|
+
const capacity = Number.isInteger(players) && players >= 1 && players <= MAX_NET_CLIENTS ? players : DEFAULT_ROOM_PLAYERS;
|
|
184
184
|
it = createRoom(key, capacity);
|
|
185
185
|
rooms.set(key, it);
|
|
186
186
|
}
|
package/src/vite.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
// with a backend gets a second chunk beside it, `backend.js`. In dev: the
|
|
7
7
|
// route that carries a model call to its provider with the key from the
|
|
8
8
|
// developer's environment, the host that runs the game's backend for the
|
|
9
|
-
// tabs that join it, and the
|
|
10
|
-
// for the game's saves. Every other build is
|
|
9
|
+
// tabs that join it, and the scripts that start each page's storage empty
|
|
10
|
+
// for the game's saves and offer `dewdrop.evaluate`. Every other build is
|
|
11
|
+
// untouched. The plugin
|
|
11
12
|
// imports nothing from Vite itself, so it works linked from a checkout as
|
|
12
13
|
// well as installed, on Vite 3 through 8.
|
|
13
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -15,6 +16,7 @@ import { resolve } from 'node:path';
|
|
|
15
16
|
import { ASSETS_DIR, BACKEND_FILE, ENTRY_FILE, PAGE_FILE, detectViteMajor, openPage, outputOptions } from './build.js';
|
|
16
17
|
import { createLlmMiddleware } from './dev.js';
|
|
17
18
|
import { loadKeys } from './env.js';
|
|
19
|
+
import { evaluateScript } from './evaluate.js';
|
|
18
20
|
import { freshScript } from './fresh.js';
|
|
19
21
|
import { DEV_ROUTE } from './route.js';
|
|
20
22
|
import { createBackendHost } from './serve.js';
|
|
@@ -140,14 +142,15 @@ export default function dew() {
|
|
|
140
142
|
},
|
|
141
143
|
},
|
|
142
144
|
// The page the dev server serves is told whether the game has a
|
|
143
|
-
// backend, which the client's `backend` reads before it joins,
|
|
144
|
-
//
|
|
145
|
-
//
|
|
145
|
+
// backend, which the client's `backend` reads before it joins, starts
|
|
146
|
+
// with its storage empty, as on Dewdrop, and offers `dewdrop.evaluate`,
|
|
147
|
+
// as a dev version's page does, from a script first in the page,
|
|
148
|
+
// before anything of its own.
|
|
146
149
|
// A build's page, which no dev server serves, is left alone.
|
|
147
150
|
transformIndexHtml(html) {
|
|
148
151
|
if (!serving) return undefined;
|
|
149
152
|
return {
|
|
150
|
-
html: withFirstScript(html, freshScript(game)),
|
|
153
|
+
html: withFirstScript(html, freshScript(game) + evaluateScript()),
|
|
151
154
|
tags: [{ tag: 'meta', attrs: { name: 'dew-backend', content: entry === null ? 'no' : 'yes' }, injectTo: 'head' }],
|
|
152
155
|
};
|
|
153
156
|
},
|