@pie-project/server-web 0.5.2
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 +91 -0
- package/dist/pie.mjs +3357 -0
- package/dist/pie_browser_bg.wasm +0 -0
- package/dist/worker.mjs +1531 -0
- package/index.d.ts +65 -0
- package/package.json +38 -0
- package/src/languages.d.ts +5 -0
- package/src/languages.mjs +27 -0
- package/src/pie.mjs +211 -0
- package/src/platform.mjs +60 -0
- package/src/transport.mjs +74 -0
- package/src/worker.mjs +234 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PieClient } from "@pie-project/client";
|
|
2
|
+
|
|
3
|
+
export { PieClient };
|
|
4
|
+
|
|
5
|
+
export interface LoadInfo {
|
|
6
|
+
worker: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LoadOptions {
|
|
10
|
+
worker?: boolean;
|
|
11
|
+
log?: string;
|
|
12
|
+
wasmUrl?: string;
|
|
13
|
+
fresh?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BootSummary {
|
|
17
|
+
model: string;
|
|
18
|
+
sku: string;
|
|
19
|
+
trace: string;
|
|
20
|
+
weight_bytes: number;
|
|
21
|
+
kv_pages: number;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function load(options?: LoadOptions): Promise<LoadInfo>;
|
|
26
|
+
|
|
27
|
+
export function bootLazy(
|
|
28
|
+
url: string,
|
|
29
|
+
config?: string,
|
|
30
|
+
onProgress?: (served: number, total: number, from: "network" | "cache") => void,
|
|
31
|
+
): Promise<BootSummary>;
|
|
32
|
+
|
|
33
|
+
export function awaitCache(): Promise<void>;
|
|
34
|
+
|
|
35
|
+
export function install(wasmBytes: Uint8Array | ArrayBuffer, manifestToml: string): Promise<string>;
|
|
36
|
+
|
|
37
|
+
export function client(): PieClient;
|
|
38
|
+
|
|
39
|
+
export function connect(): Promise<PieClient>;
|
|
40
|
+
|
|
41
|
+
export function memoryBytes(): Promise<number>;
|
|
42
|
+
|
|
43
|
+
export { registerLanguage, registeredLanguages, attachLanguages } from "./src/languages.js";
|
|
44
|
+
|
|
45
|
+
export interface StartOptions extends LoadOptions {
|
|
46
|
+
/** The URL of the `.wgpu.zt` artifact, served with Range support. */
|
|
47
|
+
model: string;
|
|
48
|
+
/** The boot config: an object, or TOML text. */
|
|
49
|
+
config?: Record<string, unknown> | string;
|
|
50
|
+
onProgress?: (served: number, total: number, from: "network" | "cache") => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One running pie in this tab, with the surface of the Node host. */
|
|
54
|
+
export class Server {
|
|
55
|
+
static start(options: StartOptions): Promise<Server>;
|
|
56
|
+
readonly summary: BootSummary;
|
|
57
|
+
readonly url: string;
|
|
58
|
+
readonly running: boolean;
|
|
59
|
+
installLanguage(language: "python" | "javascript" | string, source: Uint8Array | ArrayBuffer | URL | string): Promise<string>;
|
|
60
|
+
install(source: Uint8Array | ArrayBuffer | URL | string, manifest: string): Promise<string>;
|
|
61
|
+
connect(): Promise<PieClient>;
|
|
62
|
+
memoryBytes(): Promise<number>;
|
|
63
|
+
awaitCache(): Promise<void>;
|
|
64
|
+
shutdown(): Promise<void>;
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pie-project/server-web",
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "The browser half of @pie-project/server: the runtime and the WebGPU engine as wasm, reached through `import { Server } from \"@pie-project/server\"` under a bundler.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/pie.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./dist/pie.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./languages": {
|
|
14
|
+
"types": "./src/languages.d.ts",
|
|
15
|
+
"default": "./src/languages.mjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"index.d.ts"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "./build.sh release",
|
|
25
|
+
"bundle": "esbuild src/pie.mjs src/worker.mjs --bundle --format=esm --outdir=dist --out-extension:.js=.mjs --external:./worker.mjs --external:fs --define:__PIE_WASM__='\"./pie_browser_bg.wasm\"' --log-level=warning --log-override:duplicate-object-key=silent && cp pkg/pie_browser_bg.wasm dist/"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@pie-project/client": "^0.5.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"esbuild": "^0.28.2"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/pie-project/pie",
|
|
36
|
+
"directory": "javascript/server-web"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function registerLanguage(language: string, url: URL | string): void;
|
|
2
|
+
export function registeredLanguages(): Map<string, URL | string>;
|
|
3
|
+
export function attachLanguages(server: {
|
|
4
|
+
installLanguage(language: string, source: URL | string): Promise<string>;
|
|
5
|
+
}): Promise<() => void>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The language components a server installs when it starts. A language
|
|
2
|
+
// package registers itself on import (`import "@pie-project/language-python"`);
|
|
3
|
+
// `Server.start()` installs every registered language, and a registration
|
|
4
|
+
// after the start lands on the running servers too. The registry is one per
|
|
5
|
+
// process, however many copies of this module are loaded.
|
|
6
|
+
|
|
7
|
+
const registry = (globalThis[Symbol.for("pie.languages")] ??= { languages: new Map(), servers: new Set() });
|
|
8
|
+
|
|
9
|
+
/** Register a language component by the URL of its wasm. */
|
|
10
|
+
export function registerLanguage(language, url) {
|
|
11
|
+
registry.languages.set(language, url);
|
|
12
|
+
for (const server of registry.servers) {
|
|
13
|
+
server.installLanguage(language, url).catch((error) => console.error(`pie: ${language}: ${error.message ?? error}`));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The registered languages, language → URL. */
|
|
18
|
+
export function registeredLanguages() {
|
|
19
|
+
return new Map(registry.languages);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Install every registered language on `server`, and every one registered later while it runs. */
|
|
23
|
+
export async function attachLanguages(server) {
|
|
24
|
+
registry.servers.add(server);
|
|
25
|
+
for (const [language, url] of registry.languages) await server.installLanguage(language, url);
|
|
26
|
+
return () => registry.servers.delete(server);
|
|
27
|
+
}
|
package/src/pie.mjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { PieClient } from "@pie-project/client";
|
|
2
|
+
|
|
3
|
+
import { socketClass } from "./transport.mjs";
|
|
4
|
+
import { attachLanguages } from "./languages.mjs";
|
|
5
|
+
|
|
6
|
+
export { PieClient };
|
|
7
|
+
|
|
8
|
+
const WASM_PATH = typeof __PIE_WASM__ === "undefined" ? "../pkg/pie_browser_bg.wasm" : __PIE_WASM__;
|
|
9
|
+
|
|
10
|
+
class WorkerBackend {
|
|
11
|
+
terminate() {
|
|
12
|
+
this.worker.terminate();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
constructor(worker) {
|
|
16
|
+
this.worker = worker;
|
|
17
|
+
this.pending = new Map();
|
|
18
|
+
this.next = 0;
|
|
19
|
+
worker.onmessage = ({ data }) => {
|
|
20
|
+
const call = this.pending.get(data.id);
|
|
21
|
+
if (!call) return;
|
|
22
|
+
if (data.progress) {
|
|
23
|
+
call.onProgress?.(...data.progress);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.pending.delete(data.id);
|
|
27
|
+
if (data.ok) call.resolve(data.value);
|
|
28
|
+
else call.reject(new Error(data.error));
|
|
29
|
+
};
|
|
30
|
+
worker.onerror = (e) => {
|
|
31
|
+
console.error(`pie worker: ${e.message ?? e}`);
|
|
32
|
+
const error = new Error(`pie worker: ${e.message ?? e}`);
|
|
33
|
+
for (const call of this.pending.values()) call.reject(error);
|
|
34
|
+
this.pending.clear();
|
|
35
|
+
};
|
|
36
|
+
worker.onmessageerror = (e) => console.error("pie worker: message could not be deserialised", e);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
call(op, args = [], { transfer = [], onProgress } = {}) {
|
|
40
|
+
const id = ++this.next;
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
this.pending.set(id, { resolve, reject, onProgress });
|
|
43
|
+
this.worker.postMessage({ id, op, args }, transfer);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class LocalBackend {
|
|
49
|
+
constructor(host) {
|
|
50
|
+
this.host = host;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async call(op, args = [], { onProgress } = {}) {
|
|
54
|
+
return await this.host[op](...args, onProgress);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let backend = null;
|
|
59
|
+
let Socket = null;
|
|
60
|
+
|
|
61
|
+
function ready() {
|
|
62
|
+
if (!backend) throw new Error("pie: call load() first");
|
|
63
|
+
return backend;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function load({ worker = true, log, wasmUrl, fresh = false } = {}) {
|
|
67
|
+
if (backend && !fresh) throw new Error("pie: load() was already called");
|
|
68
|
+
if (backend) {
|
|
69
|
+
backend.terminate?.();
|
|
70
|
+
backend = null;
|
|
71
|
+
}
|
|
72
|
+
const url = new URL(wasmUrl ?? WASM_PATH, wasmUrl ? location.href : import.meta.url).href;
|
|
73
|
+
if (worker) {
|
|
74
|
+
const w = new Worker(new URL("./worker.mjs", import.meta.url), { type: "module", name: "pie" });
|
|
75
|
+
backend = new WorkerBackend(w);
|
|
76
|
+
} else {
|
|
77
|
+
const { host } = await import("./worker.mjs");
|
|
78
|
+
backend = new LocalBackend(host);
|
|
79
|
+
}
|
|
80
|
+
const info = await backend.call("load", [url, log]);
|
|
81
|
+
Socket = socketClass(backend);
|
|
82
|
+
return info;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function client() {
|
|
86
|
+
ready();
|
|
87
|
+
return new PieClient("pie://local", { WebSocket: Socket });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function connect() {
|
|
91
|
+
const c = client();
|
|
92
|
+
await c.connect();
|
|
93
|
+
return c;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function install(wasmBytes, manifestToml) {
|
|
97
|
+
const copy = new Uint8Array(wasmBytes).slice();
|
|
98
|
+
return await ready().call("install", [copy, manifestToml], { transfer: [copy.buffer] });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Hand the runtime a language component (`"python"`, `"javascript"`)
|
|
102
|
+
* from bytes; a script inferlet in that language can run once this has. */
|
|
103
|
+
export async function installLanguage(language, wasmBytes) {
|
|
104
|
+
const copy = new Uint8Array(wasmBytes).slice();
|
|
105
|
+
return await ready().call("installLanguage", [language, copy], { transfer: [copy.buffer] });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function memoryBytes() {
|
|
109
|
+
return await ready().call("memoryBytes");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function bootLazy(url, config, onProgress) {
|
|
113
|
+
return await ready().call("bootLazy", [url, config, location.href], { onProgress });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function awaitCache() {
|
|
117
|
+
return await ready().call("awaitCache", []);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function bytesOf(source) {
|
|
121
|
+
if (source instanceof Uint8Array) return source;
|
|
122
|
+
if (source instanceof ArrayBuffer) return new Uint8Array(source);
|
|
123
|
+
const response = await fetch(source);
|
|
124
|
+
if (!response.ok) throw new Error(`pie: ${source}: ${response.status} ${response.statusText}`);
|
|
125
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** One running pie in this tab, with the surface of the Node host. */
|
|
129
|
+
export class Server {
|
|
130
|
+
#summary;
|
|
131
|
+
#open = true;
|
|
132
|
+
#detach = null;
|
|
133
|
+
|
|
134
|
+
constructor(summary) {
|
|
135
|
+
this.#summary = summary;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Boot the runtime and the WebGPU engine. `model` is the URL of a
|
|
140
|
+
* `.wgpu.zt` artifact served with Range support; `config` the boot config
|
|
141
|
+
* (an object, or TOML text). Resolves once the weights are on the device,
|
|
142
|
+
* with every registered language component installed.
|
|
143
|
+
*/
|
|
144
|
+
static async start({ model, config = {}, onProgress, worker = true, log, wasmUrl } = {}) {
|
|
145
|
+
if (typeof model !== "string") throw new Error("Server.start: `model` is the URL of the artifact to serve");
|
|
146
|
+
if (!backend) await load({ worker, log, wasmUrl });
|
|
147
|
+
const summary = await bootLazy(model, typeof config === "string" ? config : JSON.stringify(config), onProgress);
|
|
148
|
+
const server = new Server(summary);
|
|
149
|
+
server.#detach = await attachLanguages(server);
|
|
150
|
+
return server;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** What the boot found: model, sku, weight bytes, kv pages. */
|
|
154
|
+
get summary() {
|
|
155
|
+
return this.#summary;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The in-page address clients connect to. */
|
|
159
|
+
get url() {
|
|
160
|
+
return "pie://local";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** True until `shutdown()`. */
|
|
164
|
+
get running() {
|
|
165
|
+
return this.#open && backend !== null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** A language component (`python`, `javascript`) from bytes or a URL. */
|
|
169
|
+
async installLanguage(language, source) {
|
|
170
|
+
this.#alive();
|
|
171
|
+
return installLanguage(language, await bytesOf(source));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** An inferlet from its component bytes (or URL) and manifest text, replacing an installed version. */
|
|
175
|
+
async install(source, manifest) {
|
|
176
|
+
this.#alive();
|
|
177
|
+
return install(await bytesOf(source), manifest);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** A `PieClient` connected in-page. */
|
|
181
|
+
async connect() {
|
|
182
|
+
this.#alive();
|
|
183
|
+
return connect();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Bytes of wasm memory in use. */
|
|
187
|
+
async memoryBytes() {
|
|
188
|
+
this.#alive();
|
|
189
|
+
return memoryBytes();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Resolves once the artifact is copied into the origin's private file system. */
|
|
193
|
+
async awaitCache() {
|
|
194
|
+
this.#alive();
|
|
195
|
+
return awaitCache();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Stop the worker and release the runtime. Idempotent. */
|
|
199
|
+
async shutdown() {
|
|
200
|
+
if (!this.#open) return;
|
|
201
|
+
this.#open = false;
|
|
202
|
+
this.#detach?.();
|
|
203
|
+
backend?.terminate?.();
|
|
204
|
+
backend = null;
|
|
205
|
+
Socket = null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#alive() {
|
|
209
|
+
if (!this.running) throw new Error("the server is shut down");
|
|
210
|
+
}
|
|
211
|
+
}
|
package/src/platform.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Fibers (wasmtime-fiber's `custom` contract, see crates/wasmtime-web):
|
|
2
|
+
// every context, the page's tick and each fiber, is one promising export
|
|
3
|
+
// invocation; a park returns a promise from the Suspending import. JSPI keeps
|
|
4
|
+
// the engine stack; Rust's shadow stack (`__stack_pointer`) is saved and
|
|
5
|
+
// restored here so the two stay paired.
|
|
6
|
+
let wasm = null;
|
|
7
|
+
let fiberEntry = null;
|
|
8
|
+
const slots = new Map();
|
|
9
|
+
let onWake = () => {};
|
|
10
|
+
|
|
11
|
+
export function attach(exports, wakeHook) {
|
|
12
|
+
wasm = exports;
|
|
13
|
+
fiberEntry = WebAssembly.promising(exports.pie_fiber_entry);
|
|
14
|
+
onWake = wakeHook;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function finishFiber(top) {
|
|
18
|
+
const parked = slots.get(top);
|
|
19
|
+
slots.delete(top);
|
|
20
|
+
if (!parked || parked.init) {
|
|
21
|
+
console.error(`[fiber] ${top} finished with no parked resumer`, parked);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
wasm.__stack_pointer.value = parked.sp;
|
|
25
|
+
parked.resolve();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pie_fiber_init(top, entry, arg0) {
|
|
29
|
+
slots.set(top >>> 0, { init: { entry, arg0 } });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const pie_fiber_switch = new WebAssembly.Suspending((top) => {
|
|
33
|
+
top = top >>> 0;
|
|
34
|
+
const parked = slots.get(top);
|
|
35
|
+
if (!parked) throw new Error(`fiber switch to unknown stack ${top}`);
|
|
36
|
+
const me = { sp: wasm.__stack_pointer.value, resolve: null };
|
|
37
|
+
const resumed = new Promise((r) => (me.resolve = r));
|
|
38
|
+
slots.set(top, me);
|
|
39
|
+
if (parked.init) {
|
|
40
|
+
const { entry, arg0 } = parked.init;
|
|
41
|
+
queueMicrotask(() => {
|
|
42
|
+
wasm.__stack_pointer.value = (top - 16) >>> 0;
|
|
43
|
+
fiberEntry(entry, arg0, top).then((ret) => finishFiber(ret >>> 0));
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
queueMicrotask(() => {
|
|
47
|
+
wasm.__stack_pointer.value = parked.sp;
|
|
48
|
+
parked.resolve();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return resumed;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export function pie_now() {
|
|
55
|
+
return performance.now();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function pie_wake() {
|
|
59
|
+
onWake();
|
|
60
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function moved(data) {
|
|
2
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
3
|
+
if (bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength && !bytes.buffer.detached) {
|
|
4
|
+
return { bytes, transfer: [bytes.buffer] };
|
|
5
|
+
}
|
|
6
|
+
const copy = bytes.slice();
|
|
7
|
+
return { bytes: copy, transfer: [copy.buffer] };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function socketClass(backend) {
|
|
11
|
+
return class PieSocket extends EventTarget {
|
|
12
|
+
static CONNECTING = 0;
|
|
13
|
+
static OPEN = 1;
|
|
14
|
+
static CLOSING = 2;
|
|
15
|
+
static CLOSED = 3;
|
|
16
|
+
|
|
17
|
+
constructor(url) {
|
|
18
|
+
super();
|
|
19
|
+
this.url = url;
|
|
20
|
+
this.binaryType = "blob";
|
|
21
|
+
this.readyState = PieSocket.CONNECTING;
|
|
22
|
+
this.onopen = null;
|
|
23
|
+
this.onmessage = null;
|
|
24
|
+
this.onerror = null;
|
|
25
|
+
this.onclose = null;
|
|
26
|
+
this.session = null;
|
|
27
|
+
backend.call("openSession").then(
|
|
28
|
+
(id) => {
|
|
29
|
+
if (this.readyState !== PieSocket.CONNECTING) {
|
|
30
|
+
backend.call("closeSession", [id]);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.session = id;
|
|
34
|
+
this.readyState = PieSocket.OPEN;
|
|
35
|
+
this.onopen?.(new Event("open"));
|
|
36
|
+
this.pump();
|
|
37
|
+
},
|
|
38
|
+
(e) => {
|
|
39
|
+
this.readyState = PieSocket.CLOSED;
|
|
40
|
+
this.onerror?.(e);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
send(data) {
|
|
46
|
+
if (this.readyState !== PieSocket.OPEN) throw new Error("socket is not open");
|
|
47
|
+
const { bytes, transfer } = moved(data);
|
|
48
|
+
backend.call("sendFrame", [this.session, bytes], { transfer }).catch((e) => this.onerror?.(e));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async pump() {
|
|
52
|
+
while (this.readyState === PieSocket.OPEN) {
|
|
53
|
+
let frames;
|
|
54
|
+
try {
|
|
55
|
+
frames = await backend.call("recvFrames", [this.session, 1000, 64]);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (this.readyState === PieSocket.OPEN) this.onerror?.(e);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
for (const frame of frames) {
|
|
61
|
+
const data = this.binaryType === "blob" ? new Blob([frame]) : frame.buffer;
|
|
62
|
+
this.onmessage?.({ data });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
close() {
|
|
68
|
+
if (this.readyState === PieSocket.CLOSED) return;
|
|
69
|
+
this.readyState = PieSocket.CLOSED;
|
|
70
|
+
if (this.session !== null) backend.call("closeSession", [this.session]);
|
|
71
|
+
this.onclose?.(new Event("close"));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|