Skip to content

Commit

Permalink
feat: add timeout feature
Browse files Browse the repository at this point in the history
Usage:

```js
socket.timeout(5000).emit("my-event", (err) => {
  if (err) {
    // the server did not acknowledge the event in the given delay
  }
});
```
  • Loading branch information
darrachequesne committed Nov 16, 2021
1 parent da0b828 commit ccf7998
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 3 deletions.
59 changes: 56 additions & 3 deletions lib/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const RESERVED_EVENTS = Object.freeze({
interface Flags {
compress?: boolean;
volatile?: boolean;
timeout?: number;
}

interface SocketReservedEvents {
Expand Down Expand Up @@ -164,9 +165,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 =
Expand All @@ -189,6 +193,35 @@ 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(() => {
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.
*
Expand Down Expand Up @@ -478,6 +511,26 @@ export class Socket<
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:
*
* ```
* socket.timeout(5000).emit("my-event", (err) => {
* if (err) {
* // the server did not acknowledge the event in the given delay
* }
* });
* ```
*
* @returns self
* @public
*/
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.
Expand Down
38 changes: 38 additions & 0 deletions test/socket.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import expect from "expect.js";
import { io } from "..";

const success = (done, socket) => {
socket.disconnect();
done();
};

describe("socket", function () {
this.timeout(70000);

Expand Down Expand Up @@ -327,4 +332,37 @@ describe("socket", function () {
});
});
});

describe("timeout", () => {
it("should timeout after the given delay when socket is not connected", (done) => {
const socket = io("/", {
autoConnect: false,
});

socket.timeout(50).emit("event", (err) => {
expect(err).to.be.an(Error);
expect(socket.sendBuffer).to.be.empty();
done();
});
});

it("should timeout after the given delay when server does not acknowledge the event", (done) => {
const socket = io("/");

socket.timeout(50).emit("unknown", (err) => {
expect(err).to.be.an(Error);
success(done, socket);
});
});

it("should not timeout after the given delay when server does acknowledge", (done) => {
const socket = io("/");

socket.timeout(50).emit("echo", 42, (err, value) => {
expect(err).to.be(null);
expect(value).to.be(42);
success(done, socket);
});
});
});
});

0 comments on commit ccf7998

Please sign in to comment.