dewdrop-sdk 0.5.1 → 0.5.3
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/evaluate.js +57 -0
- package/src/index.js +2 -1
- package/src/session.js +24 -10
- package/src/vite.js +9 -6
package/package.json
CHANGED
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/index.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// runs, `clients`, in that backend, over its port back, and `saves` into
|
|
6
6
|
// the browser's own storage, which the plugin's script empties as each
|
|
7
7
|
// page loads; `session` says when a session ends ahead of time, which here
|
|
8
|
-
// none does
|
|
8
|
+
// none does, and lets a game end its player's session, which here ends
|
|
9
|
+
// nothing; and `voice` reads the microphone through the browser's own
|
|
9
10
|
// audio worklet, into the chunks a game gets on Dewdrop.
|
|
10
11
|
export { llm } from './llm.js';
|
|
11
12
|
export { backend } from './backend.js';
|
package/src/session.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
// `session` on a developer's machine. On Dewdrop a session can end ahead of
|
|
2
|
-
// time — for maintenance, a newer version of its game published,
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// it
|
|
11
|
-
|
|
2
|
+
// time — for maintenance, a newer version of its game published, its player
|
|
3
|
+
// idle for fifteen minutes, its player away for ten — the page hidden, or
|
|
4
|
+
// another game of theirs showing — or its player choosing to play in
|
|
5
|
+
// another tab — and hears of it first, up to fifteen minutes before, a
|
|
6
|
+
// minute for an idle or an away player, and as it ends for a player playing
|
|
7
|
+
// elsewhere: `session.ending` is `{at, reason}`, `at` in milliseconds since
|
|
8
|
+
// the epoch as `Date.now()` counts and `reason` `'restart'`, `'update'`,
|
|
9
|
+
// `'idle'`, `'away'`, `'elsewhere'` or `'exit'`, and an `ending` event fires
|
|
10
|
+
// each time it changes, its withdrawal to null included, so a game can save
|
|
11
|
+
// and say so. A game ends its player's session itself with
|
|
12
|
+
// `session.exit()`, which on Dewdrop takes the player back to Dewdrop's
|
|
13
|
+
// home page while the room's other players play on, the game hearing
|
|
14
|
+
// `'exit'` as it goes; a backend has no player to send anywhere, so there it
|
|
15
|
+
// throws NotSupportedError. `npm run dev` ends no session, so here `ending`
|
|
16
|
+
// stays null and the event never fires, a game trying its handler by
|
|
17
|
+
// calling it, and `exit()` ends nothing: the console says so, once.
|
|
18
|
+
import { defineHandlers, side } from './net.js';
|
|
12
19
|
|
|
20
|
+
let told = false;
|
|
13
21
|
class Session extends EventTarget {
|
|
14
22
|
get ending() { return null; }
|
|
23
|
+
exit() {
|
|
24
|
+
if (side.backend) throw new DOMException('session.exit is unavailable in a backend', 'NotSupportedError');
|
|
25
|
+
if (told) return;
|
|
26
|
+
told = true;
|
|
27
|
+
console.info('[dewdrop] session.exit(): on Dewdrop this ends the session and takes the player back to Dewdrop’s home page; npm run dev keeps the game running');
|
|
28
|
+
}
|
|
15
29
|
}
|
|
16
30
|
defineHandlers(Session.prototype, ['ending']);
|
|
17
31
|
|
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
|
},
|