mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-10 07:28:06 -05:00
feat: allow to provide a list of transport implementations
This commit adds the ability to provide a list of transport
implementations to use when connecting to an Engine.IO server.
This can be used to use HTTP long-polling based on `fetch()`, instead
of the default implementation based on the `XMLHttpRequest` object.
```
import { Socket, Fetch, WebSocket } from "engine.io-client";
const socket = new Socket({
transports: [Fetch, WebSocket]
});
```
This is useful in some environments that do not provide a
`XMLHttpRequest` object, like Chrome extension background scripts.
> XMLHttpRequest() can't be called from a service worker, extension or
otherwise. Replace calls from your background script to
XMLHttpRequest() with calls to global fetch().
Source: https://developer.chrome.com/docs/extensions/develop/migrate/to-service-workers#replace-xmlhttprequest
Related:
- https://github.com/socketio/engine.io-client/issues/716
- https://github.com/socketio/socket.io/issues/4980
This is also useful when running the client with Deno or Bun, as it
allows to use the built-in `fetch()` method and `WebSocket` object,
instead of using the `xmlhttprequest-ssl` and `ws` Node.js packages.
Related: https://github.com/socketio/socket.io-deno/issues/12
This feature also comes with the ability to exclude the code related to
unused transports (a.k.a. "tree-shaking"):
```js
import { SocketWithoutUpgrade, WebSocket } from "engine.io-client";
const socket = new SocketWithoutUpgrade({
transports: [WebSocket]
});
```
In that case, the code related to HTTP long-polling and WebTransport
will be excluded from the final bundle.
Related: https://github.com/socketio/socket.io/discussions/4393
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
export const globalThisShim = (() => {
|
||||
if (typeof self !== "undefined") {
|
||||
return self;
|
||||
} else if (typeof window !== "undefined") {
|
||||
return window;
|
||||
} else {
|
||||
return Function("return this")();
|
||||
}
|
||||
})();
|
||||
@@ -1 +0,0 @@
|
||||
export const globalThisShim = global;
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as XMLHttpRequestModule from "xmlhttprequest-ssl";
|
||||
|
||||
export const XHR = XMLHttpRequestModule.default || XMLHttpRequestModule;
|
||||
export const nextTick = process.nextTick;
|
||||
export const globalThisShim = global;
|
||||
export const defaultBinaryType = "nodebuffer";
|
||||
|
||||
export function createCookieJar() {
|
||||
return new CookieJar();
|
||||
@@ -1,5 +1,3 @@
|
||||
import { globalThisShim as globalThis } from "../globalThis.js";
|
||||
|
||||
export const nextTick = (() => {
|
||||
const isPromiseAvailable =
|
||||
typeof Promise === "function" && typeof Promise.resolve === "function";
|
||||
@@ -10,6 +8,16 @@ export const nextTick = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
export const WebSocket = globalThis.WebSocket || globalThis.MozWebSocket;
|
||||
export const usingBrowserWebSocket = true;
|
||||
export const globalThisShim = (() => {
|
||||
if (typeof self !== "undefined") {
|
||||
return self;
|
||||
} else if (typeof window !== "undefined") {
|
||||
return window;
|
||||
} else {
|
||||
return Function("return this")();
|
||||
}
|
||||
})();
|
||||
|
||||
export const defaultBinaryType = "arraybuffer";
|
||||
|
||||
export function createCookieJar() {}
|
||||
12
lib/index.ts
12
lib/index.ts
@@ -1,13 +1,21 @@
|
||||
import { Socket } from "./socket.js";
|
||||
|
||||
export { Socket };
|
||||
export { SocketOptions } from "./socket.js";
|
||||
export {
|
||||
SocketOptions,
|
||||
SocketWithoutUpgrade,
|
||||
SocketWithUpgrade,
|
||||
} from "./socket.js";
|
||||
export const protocol = Socket.protocol;
|
||||
export { Transport, TransportError } from "./transport.js";
|
||||
export { transports } from "./transports/index.js";
|
||||
export { installTimerFunctions } from "./util.js";
|
||||
export { parse } from "./contrib/parseuri.js";
|
||||
export { nextTick } from "./transports/websocket-constructor.js";
|
||||
export { nextTick } from "./globals.node.js";
|
||||
|
||||
export { Fetch } from "./transports/polling-fetch.js";
|
||||
export { XHR as NodeXHR } from "./transports/polling-xhr.node.js";
|
||||
export { XHR } from "./transports/polling-xhr.js";
|
||||
export { WS as NodeWebSocket } from "./transports/websocket.node.js";
|
||||
export { WS as WebSocket } from "./transports/websocket.js";
|
||||
export { WT as WebTransport } from "./transports/webtransport.js";
|
||||
|
||||
433
lib/socket.ts
433
lib/socket.ts
@@ -1,13 +1,13 @@
|
||||
import { transports } from "./transports/index.js";
|
||||
import { transports as DEFAULT_TRANSPORTS } from "./transports/index.js";
|
||||
import { installTimerFunctions, byteLength } from "./util.js";
|
||||
import { decode } from "./contrib/parseqs.js";
|
||||
import { parse } from "./contrib/parseuri.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
import { Emitter } from "@socket.io/component-emitter";
|
||||
import { protocol } from "engine.io-parser";
|
||||
import type { Packet, BinaryType, PacketType, RawData } from "engine.io-parser";
|
||||
import { CloseDetails, Transport } from "./transport.js";
|
||||
import { defaultBinaryType } from "./transports/websocket-constructor.js";
|
||||
import { defaultBinaryType } from "./globals.node.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:socket"); // debug()
|
||||
|
||||
@@ -81,7 +81,7 @@ export interface SocketOptions {
|
||||
*
|
||||
* @default ['polling','websocket', 'webtransport']
|
||||
*/
|
||||
transports?: string[];
|
||||
transports?: string[] | TransportCtor[];
|
||||
|
||||
/**
|
||||
* Whether all the transports should be tested, instead of just the first one.
|
||||
@@ -231,6 +231,12 @@ export interface SocketOptions {
|
||||
protocols?: string | string[];
|
||||
}
|
||||
|
||||
type TransportCtor = { new (o: any): Transport };
|
||||
|
||||
type BaseSocketOptions = Omit<SocketOptions, "transports"> & {
|
||||
transports: TransportCtor[];
|
||||
};
|
||||
|
||||
interface HandshakeData {
|
||||
sid: string;
|
||||
upgrades: string[];
|
||||
@@ -264,7 +270,30 @@ interface WriteOptions {
|
||||
compress?: boolean;
|
||||
}
|
||||
|
||||
export class Socket extends Emitter<
|
||||
/**
|
||||
* This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
|
||||
* with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
|
||||
*
|
||||
* This class comes without upgrade mechanism, which means that it will keep the first low-level transport that
|
||||
* successfully establishes the connection.
|
||||
*
|
||||
* In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory.
|
||||
*
|
||||
* @example
|
||||
* import { SocketWithoutUpgrade, WebSocket } from "engine.io-client";
|
||||
*
|
||||
* const socket = new SocketWithoutUpgrade({
|
||||
* transports: [WebSocket]
|
||||
* });
|
||||
*
|
||||
* socket.on("open", () => {
|
||||
* socket.send("hello");
|
||||
* });
|
||||
*
|
||||
* @see SocketWithUpgrade
|
||||
* @see Socket
|
||||
*/
|
||||
export class SocketWithoutUpgrade extends Emitter<
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
SocketReservedEvents
|
||||
@@ -275,23 +304,24 @@ export class Socket extends Emitter<
|
||||
public readyState: SocketState;
|
||||
public writeBuffer: Packet[] = [];
|
||||
|
||||
protected readonly opts: BaseSocketOptions;
|
||||
protected readonly transports: string[];
|
||||
protected upgrading: boolean;
|
||||
protected setTimeoutFn: typeof setTimeout;
|
||||
|
||||
private prevBufferLen: number;
|
||||
private upgrades: string[];
|
||||
private pingInterval: number;
|
||||
private pingTimeout: number;
|
||||
private pingTimeoutTimer: NodeJS.Timer;
|
||||
private setTimeoutFn: typeof setTimeout;
|
||||
private clearTimeoutFn: typeof clearTimeout;
|
||||
private readonly beforeunloadEventListener: () => void;
|
||||
private readonly offlineEventListener: () => void;
|
||||
private upgrading: boolean;
|
||||
private maxPayload?: number;
|
||||
|
||||
private readonly opts: Partial<SocketOptions>;
|
||||
private readonly secure: boolean;
|
||||
private readonly hostname: string;
|
||||
private readonly port: string | number;
|
||||
private readonly transports: string[];
|
||||
private readonly transportsByName: Record<string, TransportCtor>;
|
||||
|
||||
static priorWebsocketSuccess: boolean;
|
||||
static protocol = protocol;
|
||||
@@ -302,9 +332,7 @@ export class Socket extends Emitter<
|
||||
* @param {String|Object} uri - uri or options
|
||||
* @param {Object} opts - options
|
||||
*/
|
||||
constructor(uri?: string, opts?: SocketOptions);
|
||||
constructor(opts: SocketOptions);
|
||||
constructor(uri?: string | SocketOptions, opts: SocketOptions = {}) {
|
||||
constructor(uri: string | BaseSocketOptions, opts: BaseSocketOptions) {
|
||||
super();
|
||||
|
||||
if (uri && "object" === typeof uri) {
|
||||
@@ -346,11 +374,14 @@ export class Socket extends Emitter<
|
||||
? "443"
|
||||
: "80");
|
||||
|
||||
this.transports = opts.transports || [
|
||||
"polling",
|
||||
"websocket",
|
||||
"webtransport",
|
||||
];
|
||||
this.transports = [];
|
||||
this.transportsByName = {};
|
||||
opts.transports.forEach((t) => {
|
||||
const transportName = t.prototype.name;
|
||||
this.transports.push(transportName);
|
||||
this.transportsByName[transportName] = t;
|
||||
});
|
||||
|
||||
this.writeBuffer = [];
|
||||
this.prevBufferLen = 0;
|
||||
|
||||
@@ -383,7 +414,6 @@ export class Socket extends Emitter<
|
||||
|
||||
// set on handshake
|
||||
this.id = null;
|
||||
this.upgrades = null;
|
||||
this.pingInterval = null;
|
||||
this.pingTimeout = null;
|
||||
|
||||
@@ -424,7 +454,7 @@ export class Socket extends Emitter<
|
||||
* @return {Transport}
|
||||
* @private
|
||||
*/
|
||||
private createTransport(name: string) {
|
||||
protected createTransport(name: string) {
|
||||
debug('creating transport "%s"', name);
|
||||
const query: any = Object.assign({}, this.opts.query);
|
||||
|
||||
@@ -452,7 +482,7 @@ export class Socket extends Emitter<
|
||||
|
||||
debug("options: %j", opts);
|
||||
|
||||
return new transports[name](opts);
|
||||
return new this.transportsByName[name](opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,7 +501,7 @@ export class Socket extends Emitter<
|
||||
|
||||
const transportName =
|
||||
this.opts.rememberUpgrade &&
|
||||
Socket.priorWebsocketSuccess &&
|
||||
SocketWithoutUpgrade.priorWebsocketSuccess &&
|
||||
this.transports.indexOf("websocket") !== -1
|
||||
? "websocket"
|
||||
: this.transports[0];
|
||||
@@ -487,7 +517,7 @@ export class Socket extends Emitter<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private setTransport(transport: Transport) {
|
||||
protected setTransport(transport: Transport) {
|
||||
debug("setting transport %s", transport.name);
|
||||
|
||||
if (this.transport) {
|
||||
@@ -506,153 +536,18 @@ export class Socket extends Emitter<
|
||||
.on("close", (reason) => this.onClose("transport close", reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Probes a transport.
|
||||
*
|
||||
* @param {String} name - transport name
|
||||
* @private
|
||||
*/
|
||||
private probe(name: string) {
|
||||
debug('probing transport "%s"', name);
|
||||
let transport = this.createTransport(name);
|
||||
let failed = false;
|
||||
|
||||
Socket.priorWebsocketSuccess = false;
|
||||
|
||||
const onTransportOpen = () => {
|
||||
if (failed) return;
|
||||
|
||||
debug('probe transport "%s" opened', name);
|
||||
transport.send([{ type: "ping", data: "probe" }]);
|
||||
transport.once("packet", (msg) => {
|
||||
if (failed) return;
|
||||
if ("pong" === msg.type && "probe" === msg.data) {
|
||||
debug('probe transport "%s" pong', name);
|
||||
this.upgrading = true;
|
||||
this.emitReserved("upgrading", transport);
|
||||
if (!transport) return;
|
||||
Socket.priorWebsocketSuccess = "websocket" === transport.name;
|
||||
|
||||
debug('pausing current transport "%s"', this.transport.name);
|
||||
this.transport.pause(() => {
|
||||
if (failed) return;
|
||||
if ("closed" === this.readyState) return;
|
||||
debug("changing transport and sending upgrade packet");
|
||||
|
||||
cleanup();
|
||||
|
||||
this.setTransport(transport);
|
||||
transport.send([{ type: "upgrade" }]);
|
||||
this.emitReserved("upgrade", transport);
|
||||
transport = null;
|
||||
this.upgrading = false;
|
||||
this.flush();
|
||||
});
|
||||
} else {
|
||||
debug('probe transport "%s" failed', name);
|
||||
const err = new Error("probe error");
|
||||
// @ts-ignore
|
||||
err.transport = transport.name;
|
||||
this.emitReserved("upgradeError", err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function freezeTransport() {
|
||||
if (failed) return;
|
||||
|
||||
// Any callback called by transport should be ignored since now
|
||||
failed = true;
|
||||
|
||||
cleanup();
|
||||
|
||||
transport.close();
|
||||
transport = null;
|
||||
}
|
||||
|
||||
// Handle any error that happens while probing
|
||||
const onerror = (err) => {
|
||||
const error = new Error("probe error: " + err);
|
||||
// @ts-ignore
|
||||
error.transport = transport.name;
|
||||
|
||||
freezeTransport();
|
||||
|
||||
debug('probe transport "%s" failed because of error: %s', name, err);
|
||||
|
||||
this.emitReserved("upgradeError", error);
|
||||
};
|
||||
|
||||
function onTransportClose() {
|
||||
onerror("transport closed");
|
||||
}
|
||||
|
||||
// When the socket is closed while we're probing
|
||||
function onclose() {
|
||||
onerror("socket closed");
|
||||
}
|
||||
|
||||
// When the socket is upgraded while we're probing
|
||||
function onupgrade(to) {
|
||||
if (transport && to.name !== transport.name) {
|
||||
debug('"%s" works - aborting "%s"', to.name, transport.name);
|
||||
freezeTransport();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all listeners on the transport and on self
|
||||
const cleanup = () => {
|
||||
transport.removeListener("open", onTransportOpen);
|
||||
transport.removeListener("error", onerror);
|
||||
transport.removeListener("close", onTransportClose);
|
||||
this.off("close", onclose);
|
||||
this.off("upgrading", onupgrade);
|
||||
};
|
||||
|
||||
transport.once("open", onTransportOpen);
|
||||
transport.once("error", onerror);
|
||||
transport.once("close", onTransportClose);
|
||||
|
||||
this.once("close", onclose);
|
||||
this.once("upgrading", onupgrade);
|
||||
|
||||
if (
|
||||
this.upgrades.indexOf("webtransport") !== -1 &&
|
||||
name !== "webtransport"
|
||||
) {
|
||||
// favor WebTransport
|
||||
this.setTimeoutFn(() => {
|
||||
if (!failed) {
|
||||
transport.open();
|
||||
}
|
||||
}, 200);
|
||||
} else {
|
||||
transport.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when connection is deemed open.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onOpen() {
|
||||
protected onOpen() {
|
||||
debug("socket open");
|
||||
this.readyState = "open";
|
||||
Socket.priorWebsocketSuccess = "websocket" === this.transport.name;
|
||||
SocketWithoutUpgrade.priorWebsocketSuccess =
|
||||
"websocket" === this.transport.name;
|
||||
this.emitReserved("open");
|
||||
this.flush();
|
||||
|
||||
// we check for `readyState` in case an `open`
|
||||
// listener already closed the socket
|
||||
if ("open" === this.readyState && this.opts.upgrade) {
|
||||
debug("starting upgrade probes");
|
||||
let i = 0;
|
||||
const l = this.upgrades.length;
|
||||
for (; i < l; i++) {
|
||||
this.probe(this.upgrades[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,11 +603,10 @@ export class Socket extends Emitter<
|
||||
* @param {Object} data - handshake obj
|
||||
* @private
|
||||
*/
|
||||
private onHandshake(data: HandshakeData) {
|
||||
protected onHandshake(data: HandshakeData) {
|
||||
this.emitReserved("handshake", data);
|
||||
this.id = data.sid;
|
||||
this.transport.query.sid = data.sid;
|
||||
this.upgrades = this.filterUpgrades(data.upgrades);
|
||||
this.pingInterval = data.pingInterval;
|
||||
this.pingTimeout = data.pingTimeout;
|
||||
this.maxPayload = data.maxPayload;
|
||||
@@ -762,7 +656,7 @@ export class Socket extends Emitter<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private flush() {
|
||||
protected flush() {
|
||||
if (
|
||||
"closed" !== this.readyState &&
|
||||
this.transport.writable &&
|
||||
@@ -928,7 +822,7 @@ export class Socket extends Emitter<
|
||||
*/
|
||||
private onError(err: Error) {
|
||||
debug("socket error %j", err);
|
||||
Socket.priorWebsocketSuccess = false;
|
||||
SocketWithoutUpgrade.priorWebsocketSuccess = false;
|
||||
|
||||
if (
|
||||
this.opts.tryAllTransports &&
|
||||
@@ -993,6 +887,177 @@ export class Socket extends Emitter<
|
||||
this.prevBufferLen = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
|
||||
* with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
|
||||
*
|
||||
* This class comes with an upgrade mechanism, which means that once the connection is established with the first
|
||||
* low-level transport, it will try to upgrade to a better transport.
|
||||
*
|
||||
* In order to allow tree-shaking, there are no transports included, that's why the `transports` option is mandatory.
|
||||
*
|
||||
* @example
|
||||
* import { SocketWithUpgrade, WebSocket } from "engine.io-client";
|
||||
*
|
||||
* const socket = new SocketWithUpgrade({
|
||||
* transports: [WebSocket]
|
||||
* });
|
||||
*
|
||||
* socket.on("open", () => {
|
||||
* socket.send("hello");
|
||||
* });
|
||||
*
|
||||
* @see SocketWithoutUpgrade
|
||||
* @see Socket
|
||||
*/
|
||||
export class SocketWithUpgrade extends SocketWithoutUpgrade {
|
||||
private upgrades: string[] = [];
|
||||
|
||||
override onOpen() {
|
||||
super.onOpen();
|
||||
|
||||
if ("open" === this.readyState && this.opts.upgrade) {
|
||||
debug("starting upgrade probes");
|
||||
let i = 0;
|
||||
const l = this.upgrades.length;
|
||||
for (; i < l; i++) {
|
||||
this.probe(this.upgrades[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probes a transport.
|
||||
*
|
||||
* @param {String} name - transport name
|
||||
* @private
|
||||
*/
|
||||
private probe(name: string) {
|
||||
debug('probing transport "%s"', name);
|
||||
let transport = this.createTransport(name);
|
||||
let failed = false;
|
||||
|
||||
SocketWithoutUpgrade.priorWebsocketSuccess = false;
|
||||
|
||||
const onTransportOpen = () => {
|
||||
if (failed) return;
|
||||
|
||||
debug('probe transport "%s" opened', name);
|
||||
transport.send([{ type: "ping", data: "probe" }]);
|
||||
transport.once("packet", (msg) => {
|
||||
if (failed) return;
|
||||
if ("pong" === msg.type && "probe" === msg.data) {
|
||||
debug('probe transport "%s" pong', name);
|
||||
this.upgrading = true;
|
||||
this.emitReserved("upgrading", transport);
|
||||
if (!transport) return;
|
||||
SocketWithoutUpgrade.priorWebsocketSuccess =
|
||||
"websocket" === transport.name;
|
||||
|
||||
debug('pausing current transport "%s"', this.transport.name);
|
||||
this.transport.pause(() => {
|
||||
if (failed) return;
|
||||
if ("closed" === this.readyState) return;
|
||||
debug("changing transport and sending upgrade packet");
|
||||
|
||||
cleanup();
|
||||
|
||||
this.setTransport(transport);
|
||||
transport.send([{ type: "upgrade" }]);
|
||||
this.emitReserved("upgrade", transport);
|
||||
transport = null;
|
||||
this.upgrading = false;
|
||||
this.flush();
|
||||
});
|
||||
} else {
|
||||
debug('probe transport "%s" failed', name);
|
||||
const err = new Error("probe error");
|
||||
// @ts-ignore
|
||||
err.transport = transport.name;
|
||||
this.emitReserved("upgradeError", err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function freezeTransport() {
|
||||
if (failed) return;
|
||||
|
||||
// Any callback called by transport should be ignored since now
|
||||
failed = true;
|
||||
|
||||
cleanup();
|
||||
|
||||
transport.close();
|
||||
transport = null;
|
||||
}
|
||||
|
||||
// Handle any error that happens while probing
|
||||
const onerror = (err) => {
|
||||
const error = new Error("probe error: " + err);
|
||||
// @ts-ignore
|
||||
error.transport = transport.name;
|
||||
|
||||
freezeTransport();
|
||||
|
||||
debug('probe transport "%s" failed because of error: %s', name, err);
|
||||
|
||||
this.emitReserved("upgradeError", error);
|
||||
};
|
||||
|
||||
function onTransportClose() {
|
||||
onerror("transport closed");
|
||||
}
|
||||
|
||||
// When the socket is closed while we're probing
|
||||
function onclose() {
|
||||
onerror("socket closed");
|
||||
}
|
||||
|
||||
// When the socket is upgraded while we're probing
|
||||
function onupgrade(to) {
|
||||
if (transport && to.name !== transport.name) {
|
||||
debug('"%s" works - aborting "%s"', to.name, transport.name);
|
||||
freezeTransport();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all listeners on the transport and on self
|
||||
const cleanup = () => {
|
||||
transport.removeListener("open", onTransportOpen);
|
||||
transport.removeListener("error", onerror);
|
||||
transport.removeListener("close", onTransportClose);
|
||||
this.off("close", onclose);
|
||||
this.off("upgrading", onupgrade);
|
||||
};
|
||||
|
||||
transport.once("open", onTransportOpen);
|
||||
transport.once("error", onerror);
|
||||
transport.once("close", onTransportClose);
|
||||
|
||||
this.once("close", onclose);
|
||||
this.once("upgrading", onupgrade);
|
||||
|
||||
if (
|
||||
this.upgrades.indexOf("webtransport") !== -1 &&
|
||||
name !== "webtransport"
|
||||
) {
|
||||
// favor WebTransport
|
||||
this.setTimeoutFn(() => {
|
||||
if (!failed) {
|
||||
transport.open();
|
||||
}
|
||||
}, 200);
|
||||
} else {
|
||||
transport.open();
|
||||
}
|
||||
}
|
||||
|
||||
override onHandshake(data: HandshakeData) {
|
||||
this.upgrades = this.filterUpgrades(data.upgrades);
|
||||
super.onHandshake(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters upgrades, returning only those matching client transports.
|
||||
@@ -1009,3 +1074,41 @@ export class Socket extends Emitter<
|
||||
return filteredUpgrades;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class provides a WebSocket-like interface to connect to an Engine.IO server. The connection will be established
|
||||
* with one of the available low-level transports, like HTTP long-polling, WebSocket or WebTransport.
|
||||
*
|
||||
* This class comes with an upgrade mechanism, which means that once the connection is established with the first
|
||||
* low-level transport, it will try to upgrade to a better transport.
|
||||
*
|
||||
* @example
|
||||
* import { Socket } from "engine.io-client";
|
||||
*
|
||||
* const socket = new Socket();
|
||||
*
|
||||
* socket.on("open", () => {
|
||||
* socket.send("hello");
|
||||
* });
|
||||
*
|
||||
* @see SocketWithoutUpgrade
|
||||
* @see SocketWithUpgrade
|
||||
*/
|
||||
export class Socket extends SocketWithUpgrade {
|
||||
constructor(uri?: string, opts?: SocketOptions);
|
||||
constructor(opts: SocketOptions);
|
||||
constructor(uri?: string | SocketOptions, opts: SocketOptions = {}) {
|
||||
const o = typeof uri === "object" ? uri : opts;
|
||||
|
||||
if (
|
||||
!o.transports ||
|
||||
(o.transports && typeof o.transports[0] === "string")
|
||||
) {
|
||||
o.transports = (o.transports || ["polling", "websocket", "webtransport"])
|
||||
.map((transportName) => DEFAULT_TRANSPORTS[transportName])
|
||||
.filter((t) => !!t);
|
||||
}
|
||||
|
||||
super(uri as string, o as BaseSocketOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ export abstract class Transport extends Emitter<
|
||||
this.opts = opts;
|
||||
this.query = opts.query;
|
||||
this.socket = opts.socket;
|
||||
this.supportsBinary = !opts.forceBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { XHR } from "./polling-xhr.js";
|
||||
import { WS } from "./websocket.js";
|
||||
import { XHR } from "./polling-xhr.node.js";
|
||||
import { WS } from "./websocket.node.js";
|
||||
import { WT } from "./webtransport.js";
|
||||
|
||||
export const transports = {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Polling } from "./polling.js";
|
||||
import { CookieJar, createCookieJar } from "./xmlhttprequest.js";
|
||||
import { CookieJar, createCookieJar } from "../globals.node.js";
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on `fetch()`
|
||||
* HTTP long-polling based on the built-in `fetch()` method.
|
||||
*
|
||||
* Usage: browser, Node.js (since v18), Deno, Bun
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/fetch
|
||||
* @see https://caniuse.com/fetch
|
||||
*/
|
||||
export class Fetch extends Polling {
|
||||
private readonly cookieJar?: CookieJar;
|
||||
|
||||
22
lib/transports/polling-xhr.node.ts
Normal file
22
lib/transports/polling-xhr.node.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as XMLHttpRequestModule from "xmlhttprequest-ssl";
|
||||
import { BaseXHR, Request, RequestOptions } from "./polling-xhr.js";
|
||||
|
||||
const XMLHttpRequest = XMLHttpRequestModule.default || XMLHttpRequestModule;
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on the `XMLHttpRequest` object provided by the `xmlhttprequest-ssl` package.
|
||||
*
|
||||
* Usage: Node.js, Deno (compat), Bun (compat)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
*/
|
||||
export class XHR extends BaseXHR {
|
||||
request(opts: Record<string, any> = {}) {
|
||||
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
|
||||
return new Request(
|
||||
(opts) => new XMLHttpRequest(opts),
|
||||
this.uri(),
|
||||
opts as RequestOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,25 @@
|
||||
import { Polling } from "./polling.js";
|
||||
import {
|
||||
CookieJar,
|
||||
createCookieJar,
|
||||
XHR as XMLHttpRequest,
|
||||
} from "./xmlhttprequest.js";
|
||||
import { Emitter } from "@socket.io/component-emitter";
|
||||
import type { SocketOptions } from "../socket.js";
|
||||
import { installTimerFunctions, pick } from "../util.js";
|
||||
import { globalThisShim as globalThis } from "../globalThis.js";
|
||||
import {
|
||||
globalThisShim as globalThis,
|
||||
createCookieJar,
|
||||
} from "../globals.node.js";
|
||||
import type { CookieJar } from "../globals.node.js";
|
||||
import type { RawData } from "engine.io-parser";
|
||||
import { hasCORS } from "../contrib/has-cors.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:polling"); // debug()
|
||||
|
||||
function empty() {}
|
||||
|
||||
const hasXHR2 = (function () {
|
||||
const xhr = new XMLHttpRequest({
|
||||
xdomain: false,
|
||||
});
|
||||
return null != xhr.responseType;
|
||||
})();
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on `XMLHttpRequest`
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
*/
|
||||
export class XHR extends Polling {
|
||||
private readonly xd: boolean;
|
||||
export abstract class BaseXHR extends Polling {
|
||||
protected readonly xd: boolean;
|
||||
protected readonly cookieJar?: CookieJar;
|
||||
|
||||
private pollXhr: any;
|
||||
private cookieJar?: CookieJar;
|
||||
|
||||
/**
|
||||
* XHR Polling constructor.
|
||||
@@ -56,11 +44,6 @@ export class XHR extends Polling {
|
||||
opts.hostname !== location.hostname) ||
|
||||
port !== opts.port;
|
||||
}
|
||||
/**
|
||||
* XHR supports binary
|
||||
*/
|
||||
const forceBase64 = opts && opts.forceBase64;
|
||||
this.supportsBinary = hasXHR2 && !forceBase64;
|
||||
|
||||
if (this.opts.withCredentials) {
|
||||
this.cookieJar = createCookieJar();
|
||||
@@ -70,13 +53,9 @@ export class XHR extends Polling {
|
||||
/**
|
||||
* Creates a request.
|
||||
*
|
||||
* @param {String} method
|
||||
* @private
|
||||
*/
|
||||
request(opts = {}) {
|
||||
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
|
||||
return new Request(this.uri(), opts);
|
||||
}
|
||||
abstract request(opts?: Record<string, any>);
|
||||
|
||||
/**
|
||||
* Sends data.
|
||||
@@ -118,8 +97,19 @@ interface RequestReservedEvents {
|
||||
error: (err: number | Error, context: unknown) => void; // context should be typed as XMLHttpRequest, but this type is not available on non-browser platforms
|
||||
}
|
||||
|
||||
export class Request extends Emitter<{}, {}, RequestReservedEvents> {
|
||||
private readonly opts: { xd; cookieJar: CookieJar } & SocketOptions;
|
||||
export type RequestOptions = SocketOptions & {
|
||||
method?: string;
|
||||
data?: RawData;
|
||||
xd: boolean;
|
||||
cookieJar: CookieJar;
|
||||
};
|
||||
|
||||
export class Request extends Emitter<
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
RequestReservedEvents
|
||||
> {
|
||||
private readonly opts: RequestOptions;
|
||||
private readonly method: string;
|
||||
private readonly uri: string;
|
||||
private readonly data: string | ArrayBuffer;
|
||||
@@ -137,7 +127,11 @@ export class Request extends Emitter<{}, {}, RequestReservedEvents> {
|
||||
* @param {Object} options
|
||||
* @package
|
||||
*/
|
||||
constructor(uri, opts) {
|
||||
constructor(
|
||||
private readonly createRequest: (opts: RequestOptions) => XMLHttpRequest,
|
||||
uri: string,
|
||||
opts: RequestOptions
|
||||
) {
|
||||
super();
|
||||
installTimerFunctions(this, opts);
|
||||
this.opts = opts;
|
||||
@@ -169,13 +163,14 @@ export class Request extends Emitter<{}, {}, RequestReservedEvents> {
|
||||
);
|
||||
opts.xdomain = !!this.opts.xd;
|
||||
|
||||
const xhr = (this.xhr = new XMLHttpRequest(opts));
|
||||
const xhr = (this.xhr = this.createRequest(opts));
|
||||
|
||||
try {
|
||||
debug("xhr open %s: %s", this.method, this.uri);
|
||||
xhr.open(this.method, this.uri, true);
|
||||
try {
|
||||
if (this.opts.extraHeaders) {
|
||||
// @ts-ignore
|
||||
xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);
|
||||
for (let i in this.opts.extraHeaders) {
|
||||
if (this.opts.extraHeaders.hasOwnProperty(i)) {
|
||||
@@ -209,6 +204,7 @@ export class Request extends Emitter<{}, {}, RequestReservedEvents> {
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 3) {
|
||||
this.opts.cookieJar?.parseCookies(
|
||||
// @ts-ignore
|
||||
xhr.getResponseHeader("set-cookie")
|
||||
);
|
||||
}
|
||||
@@ -325,3 +321,49 @@ function unloadHandler() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasXHR2 = (function () {
|
||||
const xhr = newRequest({
|
||||
xdomain: false,
|
||||
});
|
||||
return xhr && xhr.responseType !== null;
|
||||
})();
|
||||
|
||||
/**
|
||||
* HTTP long-polling based on the built-in `XMLHttpRequest` object.
|
||||
*
|
||||
* Usage: browser
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
*/
|
||||
export class XHR extends BaseXHR {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const forceBase64 = opts && opts.forceBase64;
|
||||
this.supportsBinary = hasXHR2 && !forceBase64;
|
||||
}
|
||||
|
||||
request(opts: Record<string, any> = {}) {
|
||||
Object.assign(opts, { xd: this.xd, cookieJar: this.cookieJar }, this.opts);
|
||||
return new Request(newRequest, this.uri(), opts as RequestOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function newRequest(opts) {
|
||||
const xdomain = opts.xdomain;
|
||||
|
||||
// XMLHttpRequest can be disabled on IE
|
||||
try {
|
||||
if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!xdomain) {
|
||||
try {
|
||||
return new globalThis[["Active"].concat("Object").join("X")](
|
||||
"Microsoft.XMLHTTP"
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import ws from "ws";
|
||||
|
||||
export const WebSocket = ws;
|
||||
export const usingBrowserWebSocket = false;
|
||||
export const defaultBinaryType = "nodebuffer";
|
||||
export const nextTick = process.nextTick;
|
||||
39
lib/transports/websocket.node.ts
Normal file
39
lib/transports/websocket.node.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { WebSocket } from "ws";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
import { BaseWS } from "./websocket.js";
|
||||
|
||||
/**
|
||||
* WebSocket transport based on the `WebSocket` object provided by the `ws` package.
|
||||
*
|
||||
* Usage: Node.js, Deno (compat), Bun (compat)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
* @see https://caniuse.com/mdn-api_websocket
|
||||
*/
|
||||
export class WS extends BaseWS {
|
||||
createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
) {
|
||||
return new WebSocket(uri, protocols, opts);
|
||||
}
|
||||
|
||||
doWrite(packet: Packet, data: RawData) {
|
||||
const opts: { compress?: boolean } = {};
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress;
|
||||
}
|
||||
|
||||
if (this.opts.perMessageDeflate) {
|
||||
const len =
|
||||
// @ts-ignore
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.opts.perMessageDeflate.threshold) {
|
||||
opts.compress = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.send(data, opts);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Transport } from "../transport.js";
|
||||
import { yeast } from "../contrib/yeast.js";
|
||||
import { pick } from "../util.js";
|
||||
import {
|
||||
nextTick,
|
||||
usingBrowserWebSocket,
|
||||
WebSocket,
|
||||
} from "./websocket-constructor.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
import { encodePacket } from "engine.io-parser";
|
||||
import type { Packet, RawData } from "engine.io-parser";
|
||||
import { globalThisShim as globalThis, nextTick } from "../globals.node.js";
|
||||
import debugModule from "debug"; // debug()
|
||||
|
||||
const debug = debugModule("engine.io-client:websocket"); // debug()
|
||||
|
||||
@@ -17,24 +14,8 @@ const isReactNative =
|
||||
typeof navigator.product === "string" &&
|
||||
navigator.product.toLowerCase() === "reactnative";
|
||||
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
* @see https://caniuse.com/mdn-api_websocket
|
||||
*/
|
||||
export class WS extends Transport {
|
||||
private ws: any;
|
||||
|
||||
/**
|
||||
* WebSocket transport constructor.
|
||||
*
|
||||
* @param {Object} opts - connection options
|
||||
* @protected
|
||||
*/
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
this.supportsBinary = !opts.forceBase64;
|
||||
}
|
||||
export abstract class BaseWS extends Transport {
|
||||
protected ws: any;
|
||||
|
||||
override get name() {
|
||||
return "websocket";
|
||||
@@ -71,12 +52,7 @@ export class WS extends Transport {
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws =
|
||||
usingBrowserWebSocket && !isReactNative
|
||||
? protocols
|
||||
? new WebSocket(uri, protocols)
|
||||
: new WebSocket(uri)
|
||||
: new WebSocket(uri, protocols, opts);
|
||||
this.ws = this.createSocket(uri, protocols, opts);
|
||||
} catch (err) {
|
||||
return this.emitReserved("error", err);
|
||||
}
|
||||
@@ -86,6 +62,12 @@ export class WS extends Transport {
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
abstract createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds event listeners to the socket
|
||||
*
|
||||
@@ -117,33 +99,11 @@ export class WS extends Transport {
|
||||
const lastPacket = i === packets.length - 1;
|
||||
|
||||
encodePacket(packet, this.supportsBinary, (data) => {
|
||||
// always create a new object (GH-437)
|
||||
const opts: { compress?: boolean } = {};
|
||||
if (!usingBrowserWebSocket) {
|
||||
if (packet.options) {
|
||||
opts.compress = packet.options.compress;
|
||||
}
|
||||
|
||||
if (this.opts.perMessageDeflate) {
|
||||
const len =
|
||||
// @ts-ignore
|
||||
"string" === typeof data ? Buffer.byteLength(data) : data.length;
|
||||
if (len < this.opts.perMessageDeflate.threshold) {
|
||||
opts.compress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes the websocket has already been closed but the browser didn't
|
||||
// have a chance of informing us about it yet, in that case send will
|
||||
// throw an error
|
||||
try {
|
||||
if (usingBrowserWebSocket) {
|
||||
// TypeError is thrown when passing the second argument on Safari
|
||||
this.ws.send(data);
|
||||
} else {
|
||||
this.ws.send(data, opts);
|
||||
}
|
||||
this.doWrite(packet, data);
|
||||
} catch (e) {
|
||||
debug("websocket closed before onclose event");
|
||||
}
|
||||
@@ -160,6 +120,8 @@ export class WS extends Transport {
|
||||
}
|
||||
}
|
||||
|
||||
abstract doWrite(packet: Packet, data: RawData);
|
||||
|
||||
override doClose() {
|
||||
if (typeof this.ws !== "undefined") {
|
||||
this.ws.close();
|
||||
@@ -189,3 +151,31 @@ export class WS extends Transport {
|
||||
return this.createUri(schema, query);
|
||||
}
|
||||
}
|
||||
|
||||
const WebSocketCtor = globalThis.WebSocket || globalThis.MozWebSocket;
|
||||
|
||||
/**
|
||||
* WebSocket transport based on the built-in `WebSocket` object.
|
||||
*
|
||||
* Usage: browser, Deno, Bun
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
|
||||
* @see https://caniuse.com/mdn-api_websocket
|
||||
*/
|
||||
export class WS extends BaseWS {
|
||||
createSocket(
|
||||
uri: string,
|
||||
protocols: string | string[] | undefined,
|
||||
opts: Record<string, any>
|
||||
) {
|
||||
return !isReactNative
|
||||
? protocols
|
||||
? new WebSocketCtor(uri, protocols)
|
||||
: new WebSocketCtor(uri)
|
||||
: new WebSocketCtor(uri, protocols, opts);
|
||||
}
|
||||
|
||||
doWrite(_packet: Packet, data: RawData) {
|
||||
this.ws.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transport } from "../transport.js";
|
||||
import { nextTick } from "./websocket-constructor.js";
|
||||
import { nextTick } from "../globals.node.js";
|
||||
import {
|
||||
Packet,
|
||||
createPacketDecoderStream,
|
||||
@@ -10,6 +10,10 @@ import debugModule from "debug"; // debug()
|
||||
const debug = debugModule("engine.io-client:webtransport"); // debug()
|
||||
|
||||
/**
|
||||
* WebTransport transport based on the built-in `WebTransport` object.
|
||||
*
|
||||
* Usage: browser, Node.js (with the `@fails-components/webtransport` package)
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
|
||||
* @see https://caniuse.com/webtransport
|
||||
*/
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// browser shim for xmlhttprequest module
|
||||
|
||||
import { hasCORS } from "../contrib/has-cors.js";
|
||||
import { globalThisShim as globalThis } from "../globalThis.js";
|
||||
|
||||
export function XHR(opts) {
|
||||
const xdomain = opts.xdomain;
|
||||
|
||||
// XMLHttpRequest can be disabled on IE
|
||||
try {
|
||||
if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) {
|
||||
return new XMLHttpRequest();
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (!xdomain) {
|
||||
try {
|
||||
return new globalThis[["Active"].concat("Object").join("X")](
|
||||
"Microsoft.XMLHTTP"
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
export function createCookieJar() {}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { globalThisShim as globalThis } from "./globalThis.js";
|
||||
import { globalThisShim as globalThis } from "./globals.node.js";
|
||||
|
||||
export function pick(obj, ...attr) {
|
||||
return attr.reduce((acc, k) => {
|
||||
|
||||
12
package.json
12
package.json
@@ -104,12 +104,12 @@
|
||||
},
|
||||
"browser": {
|
||||
"./test/node.js": false,
|
||||
"./build/esm/transports/xmlhttprequest.js": "./build/esm/transports/xmlhttprequest.browser.js",
|
||||
"./build/esm/transports/websocket-constructor.js": "./build/esm/transports/websocket-constructor.browser.js",
|
||||
"./build/esm/globalThis.js": "./build/esm/globalThis.browser.js",
|
||||
"./build/cjs/transports/xmlhttprequest.js": "./build/cjs/transports/xmlhttprequest.browser.js",
|
||||
"./build/cjs/transports/websocket-constructor.js": "./build/cjs/transports/websocket-constructor.browser.js",
|
||||
"./build/cjs/globalThis.js": "./build/cjs/globalThis.browser.js"
|
||||
"./build/esm/transports/polling-xhr.node.js": "./build/esm/transports/polling-xhr.js",
|
||||
"./build/esm/transports/websocket.node.js": "./build/esm/transports/websocket.js",
|
||||
"./build/esm/globals.node.js": "./build/esm/globals.js",
|
||||
"./build/cjs/transports/polling-xhr.node.js": "./build/cjs/transports/polling-xhr.js",
|
||||
"./build/cjs/transports/websocket.node.js": "./build/cjs/transports/websocket.js",
|
||||
"./build/cjs/globals.node.js": "./build/cjs/globals.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"type": "commonjs",
|
||||
"browser": {
|
||||
"ws": false,
|
||||
"./transports/xmlhttprequest.js": "./transports/xmlhttprequest.browser.js",
|
||||
"./transports/websocket-constructor.js": "./transports/websocket-constructor.browser.js",
|
||||
"./globalThis.js": "./globalThis.browser.js"
|
||||
"./transports/polling-xhr.node.js": "./transports/polling-xhr.js",
|
||||
"./transports/websocket.node.js": "./transports/websocket.js",
|
||||
"./globals.node.js": "./globals.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"type": "module",
|
||||
"browser": {
|
||||
"ws": false,
|
||||
"./transports/xmlhttprequest.js": "./transports/xmlhttprequest.browser.js",
|
||||
"./transports/websocket-constructor.js": "./transports/websocket-constructor.browser.js",
|
||||
"./globalThis.js": "./globalThis.browser.js"
|
||||
"./transports/polling-xhr.node.js": "./transports/polling-xhr.js",
|
||||
"./transports/websocket.node.js": "./transports/websocket.js",
|
||||
"./globals.node.js": "./globals.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const { exec } = require("child_process");
|
||||
const { Socket } = require("../");
|
||||
const { repeat } = require("./util");
|
||||
const expect = require("expect.js");
|
||||
const { parse } = require("../build/cjs/transports/xmlhttprequest.js");
|
||||
const { parse } = require("../build/cjs/globals.node.js");
|
||||
|
||||
describe("node.js", () => {
|
||||
describe("autoRef option", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const expect = require("expect.js");
|
||||
const { Socket } = require("../");
|
||||
const { Socket, NodeXHR, NodeWebSocket } = require("../");
|
||||
const {
|
||||
isIE11,
|
||||
isAndroid,
|
||||
@@ -97,6 +97,30 @@ describe("Socket", function () {
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with a custom transport implementation (polling)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: [NodeXHR],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("polling");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should connect with a custom transport implementation (websocket)", (done) => {
|
||||
const socket = new Socket({
|
||||
transports: [NodeWebSocket],
|
||||
});
|
||||
|
||||
socket.on("open", () => {
|
||||
expect(socket.transport.name).to.eql("websocket");
|
||||
socket.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe("fake timers", function () {
|
||||
before(function () {
|
||||
if (isIE11 || isAndroid || isEdge || isIPad) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const expect = require("expect.js");
|
||||
const XMLHttpRequest = require("../build/cjs/transports/xmlhttprequest").XHR;
|
||||
const { newRequest } = require("../build/cjs/transports/polling-xhr.node.js");
|
||||
const env = require("./support/env");
|
||||
|
||||
describe("XMLHttpRequest", () => {
|
||||
@@ -7,7 +7,7 @@ describe("XMLHttpRequest", () => {
|
||||
describe("IE8_9", () => {
|
||||
context("when xdomain is false", () => {
|
||||
it("should have same properties as XMLHttpRequest does", () => {
|
||||
const xhra = new XMLHttpRequest({
|
||||
const xhra = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: false,
|
||||
@@ -15,7 +15,7 @@ describe("XMLHttpRequest", () => {
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).to.have.property("open");
|
||||
expect(xhra).to.have.property("onreadystatechange");
|
||||
const xhrb = new XMLHttpRequest({
|
||||
const xhrb = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
@@ -23,7 +23,7 @@ describe("XMLHttpRequest", () => {
|
||||
expect(xhrb).to.be.an("object");
|
||||
expect(xhrb).to.have.property("open");
|
||||
expect(xhrb).to.have.property("onreadystatechange");
|
||||
const xhrc = new XMLHttpRequest({
|
||||
const xhrc = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
@@ -31,7 +31,7 @@ describe("XMLHttpRequest", () => {
|
||||
expect(xhrc).to.be.an("object");
|
||||
expect(xhrc).to.have.property("open");
|
||||
expect(xhrc).to.have.property("onreadystatechange");
|
||||
const xhrd = new XMLHttpRequest({
|
||||
const xhrd = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: true,
|
||||
enablesXDR: true,
|
||||
@@ -45,7 +45,7 @@ describe("XMLHttpRequest", () => {
|
||||
context("when xdomain is true", () => {
|
||||
context("when xscheme is false and enablesXDR is true", () => {
|
||||
it("should have same properties as XDomainRequest does", () => {
|
||||
const xhr = new XMLHttpRequest({
|
||||
const xhr = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
@@ -59,14 +59,14 @@ describe("XMLHttpRequest", () => {
|
||||
|
||||
context("when xscheme is true", () => {
|
||||
it("should not have open in properties", () => {
|
||||
const xhra = new XMLHttpRequest({
|
||||
const xhra = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).not.to.have.property("open");
|
||||
const xhrb = new XMLHttpRequest({
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: true,
|
||||
@@ -78,14 +78,14 @@ describe("XMLHttpRequest", () => {
|
||||
|
||||
context("when enablesXDR is false", () => {
|
||||
it("should not have open in properties", () => {
|
||||
const xhra = new XMLHttpRequest({
|
||||
const xhra = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: false,
|
||||
});
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).not.to.have.property("open");
|
||||
const xhrb = new XMLHttpRequest({
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: true,
|
||||
enablesXDR: false,
|
||||
@@ -102,7 +102,7 @@ describe("XMLHttpRequest", () => {
|
||||
describe("IE10_11", () => {
|
||||
context("when enablesXDR is true and xscheme is false", () => {
|
||||
it("should have same properties as XMLHttpRequest does", () => {
|
||||
const xhra = new XMLHttpRequest({
|
||||
const xhra = newRequest({
|
||||
xdomain: false,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
@@ -110,7 +110,7 @@ describe("XMLHttpRequest", () => {
|
||||
expect(xhra).to.be.an("object");
|
||||
expect(xhra).to.have.property("open");
|
||||
expect(xhra).to.have.property("onreadystatechange");
|
||||
const xhrb = new XMLHttpRequest({
|
||||
const xhrb = newRequest({
|
||||
xdomain: true,
|
||||
xscheme: false,
|
||||
enablesXDR: true,
|
||||
|
||||
Reference in New Issue
Block a user