aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/ws.exports.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/ws.exports.js')
-rw-r--r--src/bun.js/ws.exports.js254
1 files changed, 197 insertions, 57 deletions
diff --git a/src/bun.js/ws.exports.js b/src/bun.js/ws.exports.js
index c141c39f6..e1f042220 100644
--- a/src/bun.js/ws.exports.js
+++ b/src/bun.js/ws.exports.js
@@ -9,85 +9,225 @@ const kBunInternals = Symbol.for("::bunternal::");
const readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
const encoder = new TextEncoder();
-class BunWebSocket extends globalThis.WebSocket {
- constructor(url, ...args) {
- super(url, ...args);
- this.#wrappedHandlers = new WeakMap();
+const emittedWarnings = new Set();
+function emitWarning(type, message) {
+ if (emittedWarnings.has(type)) return;
+ emittedWarnings.add(type);
+ // process.emitWarning(message); // our printing is bad
+ console.warn("[bun] Warning:", message);
+}
+
+/*
+ * deviations: we do not implement these events
+ * - "unexpected-response"
+ * - "upgrade"
+ * - "ping"
+ * - "pong"
+ * - "redirect"
+ */
+class BunWebSocket extends EventEmitter {
+ static CONNECTING = 0;
+ static OPEN = 1;
+ static CLOSING = 2;
+ static CLOSED = 3;
+
+ #ws;
+ #paused = false;
+ #fragments = false;
+ #binaryType = "nodebuffer";
+ readyState = BunWebSocket.CONNECTING;
+
+ constructor(url, protocols, options) {
+ // deviation: we don't support anything in `options`
+ super();
+ let ws = (this.#ws = new WebSocket(url, protocols));
+ ws.binaryType = "nodebuffer"; // bun's WebSocket supports "nodebuffer"
+ ws.addEventListener("open", () => {
+ this.readyState = BunWebSocket.OPEN;
+ this.emit("open");
+ });
+ ws.addEventListener("error", err => {
+ this.readyState = BunWebSocket.CLOSED;
+ this.emit("error", err);
+ });
+ ws.addEventListener("close", ev => {
+ this.readyState = BunWebSocket.CLOSED;
+ this.emit("close", ev.code, ev.reason);
+ });
+ ws.addEventListener("message", ev => {
+ const isBinary = typeof ev.data !== "string";
+ if (isBinary) {
+ this.emit("message", this.#fragments ? [ev.data] : ev.data, isBinary);
+ } else {
+ var encoded = encoder.encode(ev.data);
+ if (this.#binaryType !== "arraybuffer") {
+ encoded = Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
+ }
+ this.emit("message", this.#fragments ? [encoded] : encoded, isBinary);
+ }
+ });
+ }
+
+ on(event, listener) {
+ if (
+ event === "unexpected-response" ||
+ event === "upgrade" ||
+ event === "ping" ||
+ event === "pong" ||
+ event === "redirect"
+ ) {
+ emitWarning(event, "ws.WebSocket '" + event + "' event is not implemented in bun");
+ }
+ return super.on(event, listener);
+ }
+
+ send(data, opts, cb) {
+ this.#ws.send(data, opts?.compress);
+ // deviation: this should be called once the data is written, not immediately
+ typeof cb === "function" && cb();
+ }
+
+ close(code, reason) {
+ this.#ws.close(code, reason);
}
- #binaryType;
- #wrappedHandlers = new WeakMap();
get binaryType() {
return this.#binaryType;
}
- set binaryType(type) {
- if (type !== "nodebuffer" && type !== "blob" && type !== "arraybuffer") {
- throw new TypeError("binaryType must be either 'blob', 'arraybuffer' or 'nodebuffer'");
- }
- if (type !== "blob") {
- super.binaryType = type;
+
+ set binaryType(value) {
+ if (value) this.#ws.binaryType = value;
+ }
+
+ set binaryType(value) {
+ if (value === "nodebuffer" || value === "arraybuffer") {
+ this.#ws.binaryType = this.#binaryType = value;
+ this.#fragments = false;
+ } else if (value === "fragments") {
+ this.#ws.binaryType = "nodebuffer";
+ this.#binaryType = "fragments";
+ this.#fragments = true;
}
- this.#binaryType = type;
}
- send(data, opts, cb) {
- super.send(data, opts?.compress);
- typeof cb === "function" && cb();
+ get protocol() {
+ return this.#ws.protocol;
}
- on(event, callback) {
- if (event === "message") {
- var handler = ({ data }) => {
- try {
- if (this.#binaryType == "blob") {
- data = new Blob([data]);
- }
- callback(data);
- } catch (e) {
- globalThis.reportError(e);
- }
- };
+ get extensions() {
+ return this.#ws.extensions;
+ }
- this.#wrappedHandlers.set(callback, handler);
- this.addEventListener(event, handler);
- } else {
- this.addEventListener(event, callback);
+ // deviation: this does not support `message` with `binaryType = "fragments"`
+ addEventListener(type, listener, options) {
+ this.#ws.addEventListener(type, listener, options);
+ }
+
+ removeEventListener(type, listener) {
+ this.#ws.removeEventListener(type, listener);
+ }
+
+ get onopen() {
+ return this.#ws.onopen;
+ }
+
+ set onopen(value) {
+ this.#ws.onopen = value;
+ }
+
+ get onerror() {
+ return this.#ws.onerror;
+ }
+
+ set onerror(value) {
+ this.#ws.onerror = value;
+ }
+
+ get onclose() {
+ return this.#ws.onclose;
+ }
+
+ set onclose(value) {
+ this.#ws.onclose = value;
+ }
+
+ get onmessage() {
+ return this.#ws.onmessage;
+ }
+
+ // deviation: this does not support `binaryType = "fragments"`
+ set onmessage(value) {
+ this.#ws.onmessage = value;
+ }
+
+ get bufferedAmount() {
+ return this.#ws.bufferedAmount;
+ }
+
+ get isPaused() {
+ return this.#paused;
+ }
+
+ ping(data, mask, cb) {
+ if (this.readyState === BunWebSocket.CONNECTING) {
+ throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
+ }
+
+ if (typeof data === "function") {
+ cb = data;
+ data = mask = undefined;
+ } else if (typeof mask === "function") {
+ cb = mask;
+ mask = undefined;
}
+
+ if (typeof data === "number") data = data.toString();
+
+ // deviation: we don't support ping
+ emitWarning("ping()", "ws.WebSocket.ping() is not implemented in bun");
+ typeof cb === "function" && cb();
}
- once(event, callback) {
- if (event === "message") {
- var handler = ({ data }) => {
- try {
- callback(data);
- } catch (e) {
- globalThis.reportError(e);
- }
- };
+ pong(data, mask, cb) {
+ if (this.readyState === BunWebSocket.CONNECTING) {
+ throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
+ }
- this.#wrappedHandlers.set(callback, handler);
- this.addEventListener(event, handler, { once: true });
- } else {
- this.addEventListener(event, callback, { once: true });
+ if (typeof data === "function") {
+ cb = data;
+ data = mask = undefined;
+ } else if (typeof mask === "function") {
+ cb = mask;
+ mask = undefined;
}
+
+ if (typeof data === "number") data = data.toString();
+
+ // deviation: we don't support pong
+ emitWarning("pong()", "ws.WebSocket.pong() is not implemented in bun");
+ typeof cb === "function" && cb();
}
- emit(event, data) {
- if (event === "message") {
- this.dispatchEvent(new MessageEvent("message", { data }));
- } else {
- this.dispatchEvent(new CustomEvent(event, { detail: data }));
+ pause() {
+ if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) {
+ return;
}
+
+ this.#paused = true;
+
+ // deviation: we dont support pause()
+ emitWarning("pause()", "ws.WebSocket.pause() is not implemented in bun");
}
- off(event, callback) {
- var wrapped = this.#wrappedHandlers.get(callback);
- if (wrapped) {
- this.removeEventListener(event, wrapped);
- this.#wrappedHandlers.delete(callback);
- } else {
- this.removeEventListener(event, callback);
+ resume() {
+ if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) {
+ return;
}
+
+ this.#paused = false;
+
+ // deviation: we dont support resume()
+ emitWarning("resume()", "ws.WebSocket.resume() is not implemented in bun");
}
}