77
packages/websocket/src/socket.io-client/contrib/backo2.ts
Normal file
77
packages/websocket/src/socket.io-client/contrib/backo2.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Initialize backoff timer with `opts`.
|
||||
*
|
||||
* - `min` initial timeout in milliseconds [100]
|
||||
* - `max` max timeout [10000]
|
||||
* - `jitter` [0]
|
||||
* - `factor` [2]
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @api public
|
||||
*/
|
||||
|
||||
export function Backoff(this: any, opts) {
|
||||
opts = opts || {}
|
||||
this.ms = opts.min || 100
|
||||
this.max = opts.max || 10000
|
||||
this.factor = opts.factor || 2
|
||||
this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0
|
||||
this.attempts = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the backoff duration.
|
||||
*
|
||||
* @return {Number}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.duration = function () {
|
||||
var ms = this.ms * Math.pow(this.factor, this.attempts++)
|
||||
if (this.jitter) {
|
||||
var rand = Math.random()
|
||||
var deviation = Math.floor(rand * this.jitter * ms)
|
||||
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation
|
||||
}
|
||||
return Math.min(ms, this.max) | 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the number of attempts.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.reset = function () {
|
||||
this.attempts = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum duration
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setMin = function (min) {
|
||||
this.ms = min
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum duration
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setMax = function (max) {
|
||||
this.max = max
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the jitter
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Backoff.prototype.setJitter = function (jitter) {
|
||||
this.jitter = jitter
|
||||
}
|
||||
@@ -4,16 +4,10 @@ import { Socket, SocketOptions } from "./socket"
|
||||
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
*/
|
||||
|
||||
module.exports = exports = lookup
|
||||
|
||||
/**
|
||||
* Managers cache.
|
||||
*/
|
||||
const cache: Record<string, Manager> = (exports.managers = {})
|
||||
const cache: Record<string, Manager> = {}
|
||||
|
||||
/**
|
||||
* Looks up an existing `Manager` for multiplexing.
|
||||
@@ -76,6 +70,15 @@ function lookup(
|
||||
return io.socket(parsed.path, opts)
|
||||
}
|
||||
|
||||
// so that "lookup" can be used both as a function (e.g. `io(...)`) and as a
|
||||
// namespace (e.g. `io.connect(...)`), for backward compatibility
|
||||
Object.assign(lookup, {
|
||||
Manager,
|
||||
Socket,
|
||||
io: lookup,
|
||||
connect: lookup,
|
||||
})
|
||||
|
||||
/**
|
||||
* Protocol version.
|
||||
*
|
||||
@@ -84,22 +87,18 @@ function lookup(
|
||||
|
||||
export { protocol } from "../socket.io-parser"
|
||||
|
||||
/**
|
||||
* `connect`.
|
||||
*
|
||||
* @param {String} uri
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.connect = lookup
|
||||
|
||||
/**
|
||||
* Expose constructors for standalone build.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
export { Manager, ManagerOptions } from "./manager"
|
||||
export { Socket } from "./socket"
|
||||
export { lookup as io, SocketOptions }
|
||||
export default lookup
|
||||
export {
|
||||
Manager,
|
||||
ManagerOptions,
|
||||
Socket,
|
||||
SocketOptions,
|
||||
lookup as io,
|
||||
lookup as connect,
|
||||
lookup as default,
|
||||
}
|
||||
|
||||
@@ -1,210 +1,26 @@
|
||||
import eio from "../engine.io-client"
|
||||
import { Socket, SocketOptions } from "./socket"
|
||||
import {
|
||||
Socket as Engine,
|
||||
SocketOptions as EngineOptions,
|
||||
installTimerFunctions,
|
||||
nextTick,
|
||||
} from "../engine.io-client"
|
||||
import { Socket, SocketOptions, DisconnectDescription } from "./socket.js"
|
||||
// import * as parser from "socket.io-parser"
|
||||
import * as parser from "../socket.io-parser"
|
||||
// import { Decoder, Encoder, Packet } from "socket.io-parser"
|
||||
import { Decoder, Encoder, Packet } from "../socket.io-parser"
|
||||
import { on } from "./on"
|
||||
import * as Backoff from "backo2"
|
||||
import { on } from "./on.js"
|
||||
import { Backoff } from "./contrib/backo2"
|
||||
import {
|
||||
DefaultEventsMap,
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
} from "./typed-events"
|
||||
Emitter,
|
||||
} from "@socket.io/component-emitter"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:manager") // debug()
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
interface EngineOptions {
|
||||
/**
|
||||
* The host that we're connecting to. Set from the URI passed when connecting
|
||||
*/
|
||||
host: string
|
||||
|
||||
/**
|
||||
* The hostname for our connection. Set from the URI passed when connecting
|
||||
*/
|
||||
hostname: string
|
||||
|
||||
/**
|
||||
* If this is a secure connection. Set from the URI passed when connecting
|
||||
*/
|
||||
secure: boolean
|
||||
|
||||
/**
|
||||
* The port for our connection. Set from the URI passed when connecting
|
||||
*/
|
||||
port: string
|
||||
|
||||
/**
|
||||
* Any query parameters in our uri. Set from the URI passed when connecting
|
||||
*/
|
||||
query: { [key: string]: string }
|
||||
|
||||
/**
|
||||
* `http.Agent` to use, defaults to `false` (NodeJS only)
|
||||
*/
|
||||
agent: string | boolean
|
||||
|
||||
/**
|
||||
* Whether the client should try to upgrade the transport from
|
||||
* long-polling to something better.
|
||||
* @default true
|
||||
*/
|
||||
upgrade: boolean
|
||||
|
||||
/**
|
||||
* Forces JSONP for polling transport.
|
||||
*/
|
||||
forceJSONP: boolean
|
||||
|
||||
/**
|
||||
* Determines whether to use JSONP when necessary for polling. If
|
||||
* disabled (by settings to false) an error will be emitted (saying
|
||||
* "No transports available") if no other transports are available.
|
||||
* If another transport is available for opening a connection (e.g.
|
||||
* WebSocket) that transport will be used instead.
|
||||
* @default true
|
||||
*/
|
||||
jsonp: boolean
|
||||
|
||||
/**
|
||||
* Forces base 64 encoding for polling transport even when XHR2
|
||||
* responseType is available and WebSocket even if the used standard
|
||||
* supports binary.
|
||||
*/
|
||||
forceBase64: boolean
|
||||
|
||||
/**
|
||||
* Enables XDomainRequest for IE8 to avoid loading bar flashing with
|
||||
* click sound. default to `false` because XDomainRequest has a flaw
|
||||
* of not sending cookie.
|
||||
* @default false
|
||||
*/
|
||||
enablesXDR: boolean
|
||||
|
||||
/**
|
||||
* The param name to use as our timestamp key
|
||||
* @default 't'
|
||||
*/
|
||||
timestampParam: string
|
||||
|
||||
/**
|
||||
* Whether to add the timestamp with each transport request. Note: this
|
||||
* is ignored if the browser is IE or Android, in which case requests
|
||||
* are always stamped
|
||||
* @default false
|
||||
*/
|
||||
timestampRequests: boolean
|
||||
|
||||
/**
|
||||
* A list of transports to try (in order). Engine.io always attempts to
|
||||
* connect directly with the first one, provided the feature detection test
|
||||
* for it passes.
|
||||
* @default ['polling','websocket']
|
||||
*/
|
||||
transports: string[]
|
||||
|
||||
/**
|
||||
* The port the policy server listens on
|
||||
* @default 843
|
||||
*/
|
||||
policyPost: number
|
||||
|
||||
/**
|
||||
* If true and if the previous websocket connection to the server succeeded,
|
||||
* the connection attempt will bypass the normal upgrade process and will
|
||||
* initially try websocket. A connection attempt following a transport error
|
||||
* will use the normal upgrade process. It is recommended you turn this on
|
||||
* only when using SSL/TLS connections, or if you know that your network does
|
||||
* not block websockets.
|
||||
* @default false
|
||||
*/
|
||||
rememberUpgrade: boolean
|
||||
|
||||
/**
|
||||
* Are we only interested in transports that support binary?
|
||||
*/
|
||||
onlyBinaryUpgrades: boolean
|
||||
|
||||
/**
|
||||
* Timeout for xhr-polling requests in milliseconds (0) (only for polling transport)
|
||||
*/
|
||||
requestTimeout: number
|
||||
|
||||
/**
|
||||
* Transport options for Node.js client (headers etc)
|
||||
*/
|
||||
transportOptions: Object
|
||||
|
||||
/**
|
||||
* (SSL) Certificate, Private key and CA certificates to use for SSL.
|
||||
* Can be used in Node.js client environment to manually specify
|
||||
* certificate information.
|
||||
*/
|
||||
pfx: string
|
||||
|
||||
/**
|
||||
* (SSL) Private key to use for SSL. Can be used in Node.js client
|
||||
* environment to manually specify certificate information.
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* (SSL) A string or passphrase for the private key or pfx. Can be
|
||||
* used in Node.js client environment to manually specify certificate
|
||||
* information.
|
||||
*/
|
||||
passphrase: string
|
||||
|
||||
/**
|
||||
* (SSL) Public x509 certificate to use. Can be used in Node.js client
|
||||
* environment to manually specify certificate information.
|
||||
*/
|
||||
cert: string
|
||||
|
||||
/**
|
||||
* (SSL) An authority certificate or array of authority certificates to
|
||||
* check the remote host against.. Can be used in Node.js client
|
||||
* environment to manually specify certificate information.
|
||||
*/
|
||||
ca: string | string[]
|
||||
|
||||
/**
|
||||
* (SSL) A string describing the ciphers to use or exclude. Consult the
|
||||
* [cipher format list]
|
||||
* (http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT) for
|
||||
* details on the format.. Can be used in Node.js client environment to
|
||||
* manually specify certificate information.
|
||||
*/
|
||||
ciphers: string
|
||||
|
||||
/**
|
||||
* (SSL) If true, the server certificate is verified against the list of
|
||||
* supplied CAs. An 'error' event is emitted if verification fails.
|
||||
* Verification happens at the connection level, before the HTTP request
|
||||
* is sent. Can be used in Node.js client environment to manually specify
|
||||
* certificate information.
|
||||
*/
|
||||
rejectUnauthorized: boolean
|
||||
|
||||
/**
|
||||
* Headers that will be passed for each request to the server (via xhr-polling and via websockets).
|
||||
* These values then can be used during handshake or for special proxies.
|
||||
*/
|
||||
extraHeaders?: { [header: string]: string }
|
||||
|
||||
/**
|
||||
* Whether to include credentials (cookies, authorization headers, TLS
|
||||
* client certificates, etc.) with cross-origin XHR polling requests
|
||||
* @default false
|
||||
*/
|
||||
withCredentials: boolean
|
||||
|
||||
/**
|
||||
* Whether to automatically close the connection whenever the beforeunload event is received.
|
||||
* @default true
|
||||
*/
|
||||
closeOnBeforeunload: boolean
|
||||
}
|
||||
|
||||
export interface ManagerOptions extends EngineOptions {
|
||||
/**
|
||||
* Should we force a new Manager for this connection?
|
||||
@@ -267,13 +83,6 @@ export interface ManagerOptions extends EngineOptions {
|
||||
*/
|
||||
autoConnect: boolean
|
||||
|
||||
/**
|
||||
* weather we should unref the reconnect timer when it is
|
||||
* create automatically
|
||||
* @default false
|
||||
*/
|
||||
autoUnref: boolean
|
||||
|
||||
/**
|
||||
* the parser to use. Defaults to an instance of the Parser that ships with socket.io.
|
||||
*/
|
||||
@@ -285,7 +94,7 @@ interface ManagerReservedEvents {
|
||||
error: (err: Error) => void
|
||||
ping: () => void
|
||||
packet: (packet: Packet) => void
|
||||
close: (reason: string) => void
|
||||
close: (reason: string, description?: DisconnectDescription) => void
|
||||
reconnect_failed: () => void
|
||||
reconnect_attempt: (attempt: number) => void
|
||||
reconnect_error: (err: Error) => void
|
||||
@@ -295,13 +104,13 @@ interface ManagerReservedEvents {
|
||||
export class Manager<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
> extends StrictEventEmitter<{}, {}, ManagerReservedEvents> {
|
||||
> extends Emitter<{}, {}, ManagerReservedEvents> {
|
||||
/**
|
||||
* The Engine.IO client instance
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public engine: any
|
||||
public engine: Engine
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -320,7 +129,9 @@ export class Manager<
|
||||
|
||||
private nsps: Record<string, Socket> = {};
|
||||
private subs: Array<ReturnType<typeof on>> = [];
|
||||
// @ts-ignore
|
||||
private backoff: Backoff
|
||||
private setTimeoutFn: typeof setTimeout
|
||||
private _reconnection: boolean
|
||||
private _reconnectionAttempts: number
|
||||
private _reconnectionDelay: number
|
||||
@@ -358,6 +169,7 @@ export class Manager<
|
||||
|
||||
opts.path = opts.path || "/socket.io"
|
||||
this.opts = opts
|
||||
installTimerFunctions(this, opts)
|
||||
this.reconnection(opts.reconnection !== false)
|
||||
this.reconnectionAttempts(opts.reconnectionAttempts || Infinity)
|
||||
this.reconnectionDelay(opts.reconnectionDelay || 1000)
|
||||
@@ -507,8 +319,7 @@ export class Manager<
|
||||
if (~this._readyState.indexOf("open")) return this
|
||||
|
||||
debug("opening %s", this.uri)
|
||||
// @ts-ignore
|
||||
this.engine = eio(this.uri, this.opts)
|
||||
this.engine = new Engine(this.uri, this.opts)
|
||||
const socket = this.engine
|
||||
const self = this
|
||||
this._readyState = "opening"
|
||||
@@ -543,10 +354,11 @@ export class Manager<
|
||||
}
|
||||
|
||||
// set timer
|
||||
const timer = setTimeout(() => {
|
||||
const timer = this.setTimeoutFn(() => {
|
||||
debug("connect attempt timed out after %d", timeout)
|
||||
openSubDestroy()
|
||||
socket.close()
|
||||
// @ts-ignore
|
||||
socket.emit("error", new Error("timeout"))
|
||||
}, timeout)
|
||||
|
||||
@@ -616,7 +428,11 @@ export class Manager<
|
||||
* @private
|
||||
*/
|
||||
private ondata(data): void {
|
||||
this.decoder.add(data)
|
||||
try {
|
||||
this.decoder.add(data)
|
||||
} catch (e) {
|
||||
this.onclose("parse error", e as Error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -625,7 +441,10 @@ export class Manager<
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet): void {
|
||||
this.emitReserved("packet", packet)
|
||||
// the nextTick call prevents an exception in a user-provided event listener from triggering a disconnection due to a "parse error"
|
||||
nextTick(() => {
|
||||
this.emitReserved("packet", packet)
|
||||
}, this.setTimeoutFn)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,13 +532,7 @@ export class Manager<
|
||||
debug("disconnect")
|
||||
this.skipReconnect = true
|
||||
this._reconnecting = false
|
||||
if ("opening" === this._readyState) {
|
||||
// `onclose` will not fire because
|
||||
// an open event never happened
|
||||
this.cleanup()
|
||||
}
|
||||
this.backoff.reset()
|
||||
this._readyState = "closed"
|
||||
this.onclose("forced close")
|
||||
if (this.engine) this.engine.close()
|
||||
}
|
||||
|
||||
@@ -737,13 +550,13 @@ export class Manager<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: string): void {
|
||||
debug("onclose")
|
||||
private onclose(reason: string, description?: DisconnectDescription): void {
|
||||
debug("closed due to %s", reason)
|
||||
|
||||
this.cleanup()
|
||||
this.backoff.reset()
|
||||
this._readyState = "closed"
|
||||
this.emitReserved("close", reason)
|
||||
this.emitReserved("close", reason, description)
|
||||
|
||||
if (this._reconnection && !this.skipReconnect) {
|
||||
this.reconnect()
|
||||
@@ -770,7 +583,7 @@ export class Manager<
|
||||
debug("will wait %dms before reconnect attempt", delay)
|
||||
|
||||
this._reconnecting = true
|
||||
const timer = setTimeout(() => {
|
||||
const timer = this.setTimeoutFn(() => {
|
||||
if (self.skipReconnect) return
|
||||
|
||||
debug("attempting reconnect")
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// import type * as Emitter from "component-emitter";
|
||||
import { EventEmitter } from "events"
|
||||
import { StrictEventEmitter } from "./typed-events"
|
||||
import { Emitter } from "@socket.io/component-emitter"
|
||||
|
||||
export function on(
|
||||
obj: EventEmitter | StrictEventEmitter<any, any>,
|
||||
obj: Emitter<any, any>,
|
||||
ev: string,
|
||||
fn: (err?: any) => any
|
||||
): VoidFunction {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// import { Packet, PacketType } from "socket.io-parser"
|
||||
import { Packet, PacketType } from "../socket.io-parser"
|
||||
import { on } from "./on"
|
||||
import { Manager } from "./manager"
|
||||
import { on } from "./on.js"
|
||||
import { Manager } from "./manager.js"
|
||||
import {
|
||||
DefaultEventsMap,
|
||||
EventNames,
|
||||
EventParams,
|
||||
EventsMap,
|
||||
StrictEventEmitter,
|
||||
} from "./typed-events"
|
||||
Emitter,
|
||||
} from "@socket.io/component-emitter"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:socket") // debug()
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
export interface SocketOptions {
|
||||
@@ -35,26 +38,110 @@ const RESERVED_EVENTS = Object.freeze({
|
||||
interface Flags {
|
||||
compress?: boolean
|
||||
volatile?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export type DisconnectDescription =
|
||||
| Error
|
||||
| {
|
||||
description: string
|
||||
context?: CloseEvent | XMLHttpRequest
|
||||
}
|
||||
|
||||
interface SocketReservedEvents {
|
||||
connect: () => void
|
||||
connect_error: (err: Error) => void
|
||||
disconnect: (reason: Socket.DisconnectReason) => void
|
||||
disconnect: (
|
||||
reason: Socket.DisconnectReason,
|
||||
description?: DisconnectDescription
|
||||
) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* A Socket is the fundamental class for interacting with the server.
|
||||
*
|
||||
* A Socket belongs to a certain Namespace (by default /) and uses an underlying {@link Manager} to communicate.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log("connected");
|
||||
* });
|
||||
*
|
||||
* // send an event to the server
|
||||
* socket.emit("foo", "bar");
|
||||
*
|
||||
* socket.on("foobar", () => {
|
||||
* // an event was received from the server
|
||||
* });
|
||||
*
|
||||
* // upon disconnection
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* console.log(`disconnected due to ${reason}`);
|
||||
* });
|
||||
*/
|
||||
export class Socket<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
> extends StrictEventEmitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||
> extends Emitter<ListenEvents, EmitEvents, SocketReservedEvents> {
|
||||
public readonly io: Manager<ListenEvents, EmitEvents>
|
||||
|
||||
/**
|
||||
* A unique identifier for the session.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* console.log(socket.id); // undefined
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.id); // "G5p5..."
|
||||
* });
|
||||
*/
|
||||
public id: string
|
||||
public connected: boolean
|
||||
public disconnected: boolean
|
||||
|
||||
/**
|
||||
* Whether the socket is currently connected to the server.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.connected); // true
|
||||
* });
|
||||
*
|
||||
* socket.on("disconnect", () => {
|
||||
* console.log(socket.connected); // false
|
||||
* });
|
||||
*/
|
||||
public connected: boolean = false;
|
||||
|
||||
/**
|
||||
* Credentials that are sent when accessing a namespace.
|
||||
*
|
||||
* @example
|
||||
* const socket = io({
|
||||
* auth: {
|
||||
* token: "abcd"
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // or with a function
|
||||
* const socket = io({
|
||||
* auth: (cb) => {
|
||||
* cb({ token: localStorage.token })
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
public auth: { [key: string]: any } | ((cb: (data: object) => void) => void)
|
||||
/**
|
||||
* Buffer for packets received before the CONNECT packet
|
||||
*/
|
||||
public receiveBuffer: Array<ReadonlyArray<any>> = [];
|
||||
/**
|
||||
* Buffer for packets that will be sent once the socket is connected
|
||||
*/
|
||||
public sendBuffer: Array<Packet> = [];
|
||||
|
||||
private readonly nsp: string
|
||||
@@ -64,29 +151,39 @@ export class Socket<
|
||||
private flags: Flags = {};
|
||||
private subs?: Array<VoidFunction>
|
||||
private _anyListeners: Array<(...args: any[]) => void>
|
||||
private _anyOutgoingListeners: Array<(...args: any[]) => void>
|
||||
|
||||
/**
|
||||
* `Socket` constructor.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
constructor(io: Manager, nsp: string, opts?: Partial<SocketOptions>) {
|
||||
super()
|
||||
this.io = io
|
||||
this.nsp = nsp
|
||||
this.ids = 0
|
||||
this.acks = {}
|
||||
this.receiveBuffer = []
|
||||
this.sendBuffer = []
|
||||
this.connected = false
|
||||
this.disconnected = true
|
||||
this.flags = {}
|
||||
if (opts && opts.auth) {
|
||||
this.auth = opts.auth
|
||||
}
|
||||
if (this.io._autoConnect) this.open()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the socket is currently disconnected
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("connect", () => {
|
||||
* console.log(socket.disconnected); // false
|
||||
* });
|
||||
*
|
||||
* socket.on("disconnect", () => {
|
||||
* console.log(socket.disconnected); // true
|
||||
* });
|
||||
*/
|
||||
public get disconnected(): boolean {
|
||||
return !this.connected
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to open, close and packet events
|
||||
*
|
||||
@@ -105,7 +202,21 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the Socket will try to reconnect when its Manager connects or reconnects
|
||||
* Whether the Socket will try to reconnect when its Manager connects or reconnects.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* console.log(socket.active); // true
|
||||
*
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* if (reason === "io server disconnect") {
|
||||
* // the disconnection was initiated by the server, you need to manually reconnect
|
||||
* console.log(socket.active); // false
|
||||
* }
|
||||
* // else the socket will automatically try to reconnect
|
||||
* console.log(socket.active); // true
|
||||
* });
|
||||
*/
|
||||
public get active(): boolean {
|
||||
return !!this.subs
|
||||
@@ -114,7 +225,12 @@ export class Socket<
|
||||
/**
|
||||
* "Opens" the socket.
|
||||
*
|
||||
* @public
|
||||
* @example
|
||||
* const socket = io({
|
||||
* autoConnect: false
|
||||
* });
|
||||
*
|
||||
* socket.connect();
|
||||
*/
|
||||
public connect(): this {
|
||||
if (this.connected) return this
|
||||
@@ -126,7 +242,7 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for connect()
|
||||
* Alias for {@link connect()}.
|
||||
*/
|
||||
public open(): this {
|
||||
return this.connect()
|
||||
@@ -135,8 +251,17 @@ export class Socket<
|
||||
/**
|
||||
* Sends a `message` event.
|
||||
*
|
||||
* This method mimics the WebSocket.send() method.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
|
||||
*
|
||||
* @example
|
||||
* socket.send("hello");
|
||||
*
|
||||
* // this is equivalent to
|
||||
* socket.emit("message", "hello");
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public send(...args: any[]): this {
|
||||
args.unshift("message")
|
||||
@@ -149,15 +274,25 @@ export class Socket<
|
||||
* Override `emit`.
|
||||
* If the event is in `events`, it's emitted normally.
|
||||
*
|
||||
* @example
|
||||
* socket.emit("hello", "world");
|
||||
*
|
||||
* // all serializable datastructures are supported (no need to call JSON.stringify)
|
||||
* socket.emit("hello", 1, "2", { 3: ["4"], 5: Uint8Array.from([6]) });
|
||||
*
|
||||
* // with an acknowledgement from the server
|
||||
* socket.emit("hello", "world", (val) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): this {
|
||||
if (RESERVED_EVENTS.hasOwnProperty(ev)) {
|
||||
throw new Error('"' + ev + '" is a reserved event name')
|
||||
throw new Error('"' + ev.toString() + '" is a reserved event name')
|
||||
}
|
||||
|
||||
args.unshift(ev)
|
||||
@@ -171,9 +306,12 @@ export class Socket<
|
||||
|
||||
// event ack callback
|
||||
if ("function" === typeof args[args.length - 1]) {
|
||||
debug("emitting packet with ack id %d", this.ids)
|
||||
this.acks[this.ids] = args.pop()
|
||||
packet.id = this.ids++
|
||||
const id = this.ids++
|
||||
debug("emitting packet with ack id %d", id)
|
||||
|
||||
const ack = args.pop() as Function
|
||||
this._registerAckCallback(id, ack)
|
||||
packet.id = id
|
||||
}
|
||||
|
||||
const isTransportWritable =
|
||||
@@ -186,6 +324,7 @@ export class Socket<
|
||||
if (discardPacket) {
|
||||
debug("discard packet as the transport is not currently writable")
|
||||
} else if (this.connected) {
|
||||
this.notifyOutgoingListeners(packet)
|
||||
this.packet(packet)
|
||||
} else {
|
||||
this.sendBuffer.push(packet)
|
||||
@@ -196,6 +335,36 @@ export class Socket<
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private _registerAckCallback(id: number, ack: Function) {
|
||||
const timeout = this.flags.timeout
|
||||
if (timeout === undefined) {
|
||||
this.acks[id] = ack
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const timer = this.io.setTimeoutFn(() => {
|
||||
delete this.acks[id]
|
||||
for (let i = 0; i < this.sendBuffer.length; i++) {
|
||||
if (this.sendBuffer[i].id === id) {
|
||||
debug("removing packet with ack id %d from the buffer", id)
|
||||
this.sendBuffer.splice(i, 1)
|
||||
}
|
||||
}
|
||||
debug("event with ack id %d has timed out after %d ms", id, timeout)
|
||||
ack.call(this, new Error("operation has timed out"))
|
||||
}, timeout)
|
||||
|
||||
this.acks[id] = (...args) => {
|
||||
// @ts-ignore
|
||||
this.io.clearTimeoutFn(timer)
|
||||
ack.apply(this, [null, ...args])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
*
|
||||
@@ -239,14 +408,17 @@ export class Socket<
|
||||
* Called upon engine `close`.
|
||||
*
|
||||
* @param reason
|
||||
* @param description
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: Socket.DisconnectReason): void {
|
||||
private onclose(
|
||||
reason: Socket.DisconnectReason,
|
||||
description?: DisconnectDescription
|
||||
): void {
|
||||
debug("close (%s)", reason)
|
||||
this.connected = false
|
||||
this.disconnected = true
|
||||
delete this.id
|
||||
this.emitReserved("disconnect", reason)
|
||||
this.emitReserved("disconnect", reason, description)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,17 +448,11 @@ export class Socket<
|
||||
break
|
||||
|
||||
case PacketType.EVENT:
|
||||
this.onevent(packet)
|
||||
break
|
||||
|
||||
case PacketType.BINARY_EVENT:
|
||||
this.onevent(packet)
|
||||
break
|
||||
|
||||
case PacketType.ACK:
|
||||
this.onack(packet)
|
||||
break
|
||||
|
||||
case PacketType.BINARY_ACK:
|
||||
this.onack(packet)
|
||||
break
|
||||
@@ -296,6 +462,7 @@ export class Socket<
|
||||
break
|
||||
|
||||
case PacketType.CONNECT_ERROR:
|
||||
this.destroy()
|
||||
const err = new Error(packet.data.message)
|
||||
// @ts-ignore
|
||||
err.data = packet.data.data
|
||||
@@ -386,7 +553,6 @@ export class Socket<
|
||||
debug("socket connected with id %s", id)
|
||||
this.id = id
|
||||
this.connected = true
|
||||
this.disconnected = false
|
||||
this.emitBuffered()
|
||||
this.emitReserved("connect")
|
||||
}
|
||||
@@ -400,7 +566,10 @@ export class Socket<
|
||||
this.receiveBuffer.forEach((args) => this.emitEvent(args))
|
||||
this.receiveBuffer = []
|
||||
|
||||
this.sendBuffer.forEach((packet) => this.packet(packet))
|
||||
this.sendBuffer.forEach((packet) => {
|
||||
this.notifyOutgoingListeners(packet)
|
||||
this.packet(packet)
|
||||
})
|
||||
this.sendBuffer = []
|
||||
}
|
||||
|
||||
@@ -432,10 +601,20 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the socket manually.
|
||||
* Disconnects the socket manually. In that case, the socket will not try to reconnect.
|
||||
*
|
||||
* If this is the last active Socket instance of the {@link Manager}, the low-level connection will be closed.
|
||||
*
|
||||
* @example
|
||||
* const socket = io();
|
||||
*
|
||||
* socket.on("disconnect", (reason) => {
|
||||
* // console.log(reason); prints "io client disconnect"
|
||||
* });
|
||||
*
|
||||
* socket.disconnect();
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public disconnect(): this {
|
||||
if (this.connected) {
|
||||
@@ -454,10 +633,9 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for disconnect()
|
||||
* Alias for {@link disconnect()}.
|
||||
*
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public close(): this {
|
||||
return this.disconnect()
|
||||
@@ -466,9 +644,11 @@ export class Socket<
|
||||
/**
|
||||
* Sets the compress flag.
|
||||
*
|
||||
* @example
|
||||
* socket.compress(false).emit("hello");
|
||||
*
|
||||
* @param compress - if `true`, compresses the sending data
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): this {
|
||||
this.flags.compress = compress
|
||||
@@ -479,20 +659,44 @@ export class Socket<
|
||||
* Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not
|
||||
* ready to send messages.
|
||||
*
|
||||
* @example
|
||||
* socket.volatile.emit("hello"); // the server may or may not receive it
|
||||
*
|
||||
* @returns self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): this {
|
||||
this.flags.volatile = true
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the callback will be called with an error when the
|
||||
* given number of milliseconds have elapsed without an acknowledgement from the server:
|
||||
*
|
||||
* @example
|
||||
* socket.timeout(5000).emit("my-event", (err) => {
|
||||
* if (err) {
|
||||
* // the server did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @returns self
|
||||
*/
|
||||
public timeout(timeout: number): this {
|
||||
this.flags.timeout = timeout
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback.
|
||||
*
|
||||
* @example
|
||||
* socket.onAny((event, ...args) => {
|
||||
* console.log(`got ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public onAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || []
|
||||
@@ -504,8 +708,12 @@ export class Socket<
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback. The listener is added to the beginning of the listeners array.
|
||||
*
|
||||
* @example
|
||||
* socket.prependAny((event, ...args) => {
|
||||
* console.log(`got event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public prependAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || []
|
||||
@@ -516,8 +724,20 @@ export class Socket<
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
*
|
||||
* @example
|
||||
* const catchAllListener = (event, ...args) => {
|
||||
* console.log(`got event ${event}`);
|
||||
* }
|
||||
*
|
||||
* socket.onAny(catchAllListener);
|
||||
*
|
||||
* // remove a specific listener
|
||||
* socket.offAny(catchAllListener);
|
||||
*
|
||||
* // or remove all listeners
|
||||
* socket.offAny();
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public offAny(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyListeners) {
|
||||
@@ -540,12 +760,108 @@ export class Socket<
|
||||
/**
|
||||
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
|
||||
* e.g. to remove listeners.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public listenersAny() {
|
||||
return this._anyListeners || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback.
|
||||
*
|
||||
* Note: acknowledgements sent to the server are not included.
|
||||
*
|
||||
* @example
|
||||
* socket.onAnyOutgoing((event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public onAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || []
|
||||
this._anyOutgoingListeners.push(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback. The listener is added to the beginning of the listeners array.
|
||||
*
|
||||
* Note: acknowledgements sent to the server are not included.
|
||||
*
|
||||
* @example
|
||||
* socket.prependAnyOutgoing((event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* });
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public prependAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || []
|
||||
this._anyOutgoingListeners.unshift(listener)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
*
|
||||
* @example
|
||||
* const catchAllListener = (event, ...args) => {
|
||||
* console.log(`sent event ${event}`);
|
||||
* }
|
||||
*
|
||||
* socket.onAnyOutgoing(catchAllListener);
|
||||
*
|
||||
* // remove a specific listener
|
||||
* socket.offAnyOutgoing(catchAllListener);
|
||||
*
|
||||
* // or remove all listeners
|
||||
* socket.offAnyOutgoing();
|
||||
*
|
||||
* @param [listener] - the catch-all listener (optional)
|
||||
*/
|
||||
public offAnyOutgoing(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyOutgoingListeners) {
|
||||
return this
|
||||
}
|
||||
if (listener) {
|
||||
const listeners = this._anyOutgoingListeners
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (listener === listeners[i]) {
|
||||
listeners.splice(i, 1)
|
||||
return this
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._anyOutgoingListeners = []
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
|
||||
* e.g. to remove listeners.
|
||||
*/
|
||||
public listenersAnyOutgoing() {
|
||||
return this._anyOutgoingListeners || []
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners for each packet sent
|
||||
*
|
||||
* @param packet
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private notifyOutgoingListeners(packet: Packet) {
|
||||
if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
|
||||
const listeners = this._anyOutgoingListeners.slice()
|
||||
for (const listener of listeners) {
|
||||
listener.apply(this, packet.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Socket {
|
||||
@@ -555,4 +871,5 @@ export namespace Socket {
|
||||
| "ping timeout"
|
||||
| "transport close"
|
||||
| "transport error"
|
||||
| "parse error"
|
||||
}
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
import { EventEmitter } from "events"
|
||||
|
||||
/**
|
||||
* An events map is an interface that maps event names to their value, which
|
||||
* represents the type of the `on` listener.
|
||||
*/
|
||||
export interface EventsMap {
|
||||
[event: string]: any
|
||||
}
|
||||
|
||||
/**
|
||||
* The default events map, used if no EventsMap is given. Using this EventsMap
|
||||
* is equivalent to accepting all event names, and any data.
|
||||
*/
|
||||
export interface DefaultEventsMap {
|
||||
[event: string]: (...args: any[]) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a union type containing all the keys of an event map.
|
||||
*/
|
||||
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol)
|
||||
|
||||
/** The tuple type representing the parameters of an event listener */
|
||||
export type EventParams<
|
||||
Map extends EventsMap,
|
||||
Ev extends EventNames<Map>
|
||||
> = Parameters<Map[Ev]>
|
||||
|
||||
/**
|
||||
* The event names that are either in ReservedEvents or in UserEvents
|
||||
*/
|
||||
export type ReservedOrUserEventNames<
|
||||
ReservedEventsMap extends EventsMap,
|
||||
UserEvents extends EventsMap
|
||||
> = EventNames<ReservedEventsMap> | EventNames<UserEvents>
|
||||
|
||||
/**
|
||||
* Type of a listener of a user event or a reserved event. If `Ev` is in
|
||||
* `ReservedEvents`, the reserved event listener is returned.
|
||||
*/
|
||||
export type ReservedOrUserListener<
|
||||
ReservedEvents extends EventsMap,
|
||||
UserEvents extends EventsMap,
|
||||
Ev extends ReservedOrUserEventNames<ReservedEvents, UserEvents>
|
||||
> = FallbackToUntypedListener<
|
||||
Ev extends EventNames<ReservedEvents>
|
||||
? ReservedEvents[Ev]
|
||||
: Ev extends EventNames<UserEvents>
|
||||
? UserEvents[Ev]
|
||||
: never
|
||||
>
|
||||
|
||||
/**
|
||||
* Returns an untyped listener type if `T` is `never`; otherwise, returns `T`.
|
||||
*
|
||||
* This is a hack to mitigate https://github.com/socketio/socket.io/issues/3833.
|
||||
* Needed because of https://github.com/microsoft/TypeScript/issues/41778
|
||||
*/
|
||||
type FallbackToUntypedListener<T> = [T] extends [never]
|
||||
? (...args: any[]) => void
|
||||
: T
|
||||
|
||||
/**
|
||||
* Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type
|
||||
* parameters for mappings of event names to event data types, and strictly
|
||||
* types method calls to the `EventEmitter` according to these event maps.
|
||||
*
|
||||
* @typeParam ListenEvents - `EventsMap` of user-defined events that can be
|
||||
* listened to with `on` or `once`
|
||||
* @typeParam EmitEvents - `EventsMap` of user-defined events that can be
|
||||
* emitted with `emit`
|
||||
* @typeParam ReservedEvents - `EventsMap` of reserved events, that can be
|
||||
* emitted by socket.io with `emitReserved`, and can be listened to with
|
||||
* `listen`.
|
||||
*/
|
||||
export abstract class StrictEventEmitter<
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap,
|
||||
ReservedEvents extends EventsMap = {}
|
||||
> extends EventEmitter {
|
||||
/**
|
||||
* Adds the `listener` function as an event listener for `ev`.
|
||||
*
|
||||
* @param ev Name of the event
|
||||
* @param listener Callback function
|
||||
*/
|
||||
on<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
|
||||
ev: Ev,
|
||||
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
|
||||
): this {
|
||||
super.on(ev as string, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a one-time `listener` function as an event listener for `ev`.
|
||||
*
|
||||
* @param ev Name of the event
|
||||
* @param listener Callback function
|
||||
*/
|
||||
once<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
|
||||
ev: Ev,
|
||||
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
|
||||
): this {
|
||||
super.once(ev as string, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
*
|
||||
* @param ev Name of the event
|
||||
* @param args Values to send to listeners of this event
|
||||
*/
|
||||
// @ts-ignore
|
||||
emit<Ev extends EventNames<EmitEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): this {
|
||||
super.emit(ev as string, ...args)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a reserved event.
|
||||
*
|
||||
* This method is `protected`, so that only a class extending
|
||||
* `StrictEventEmitter` can emit its own reserved events.
|
||||
*
|
||||
* @param ev Reserved event name
|
||||
* @param args Arguments to emit along with the event
|
||||
*/
|
||||
protected emitReserved<Ev extends EventNames<ReservedEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<ReservedEvents, Ev>
|
||||
): this {
|
||||
super.emit(ev as string, ...args)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the listeners listening to an event.
|
||||
*
|
||||
* @param event Event name
|
||||
* @returns Array of listeners subscribed to `event`
|
||||
*/
|
||||
listeners<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
|
||||
event: Ev
|
||||
): ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>[] {
|
||||
return super.listeners(event as string) as ReservedOrUserListener<
|
||||
ReservedEvents,
|
||||
ListenEvents,
|
||||
Ev
|
||||
>[]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as parseuri from "parseuri"
|
||||
// import { parse } from "engine.io-client"
|
||||
import { parse } from "../engine.io-client"
|
||||
// import debugModule from "debug" // debug()
|
||||
|
||||
// const debug = debugModule("socket.io-client:url"); // debug()
|
||||
const debug = require("../debug")("socket.io-client")
|
||||
|
||||
type ParsedUrl = {
|
||||
@@ -67,7 +70,7 @@ export function url(
|
||||
|
||||
// parse
|
||||
debug("parse %s", uri)
|
||||
obj = parseuri(uri) as ParsedUrl
|
||||
obj = parse(uri) as ParsedUrl
|
||||
}
|
||||
|
||||
// make sure we treat `localhost:80` and `localhost` equally
|
||||
|
||||
Reference in New Issue
Block a user