Packages
phoenix
1.4.1
1.8.9
1.8.8
1.8.7
1.8.6
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.8.0-rc.4
1.8.0-rc.3
1.8.0-rc.2
1.8.0-rc.1
1.8.0-rc.0
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.7.0-rc.3
1.7.0-rc.2
1.7.0-rc.1
1.7.0-rc.0
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-rc.1
1.6.0-rc.0
1.5.15
1.5.14
1.5.13
1.5.12
1.5.11
1.5.10
1.5.9
1.5.8
1.5.7
1.5.6
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.5.0-rc.0
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.12
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-rc.3
1.4.0-rc.2
1.4.0-rc.1
1.4.0-rc.0
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-rc.3
1.3.0-rc.2
1.3.0-rc.1
1.3.0-rc.0
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.2.0-rc.1
1.2.0-rc.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.17.1
0.17.0
0.16.1
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.1
0.3.0
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Productive. Reliable. Fast. A productive web framework that does not compromise speed or maintainability.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
assets/test/socket_test.js
import assert from "assert"
import jsdom from "jsdom"
import sinon from "sinon"
import {WebSocket, Server as WebSocketServer} from "mock-socket"
import {encode, decode} from "./serializer"
import {Socket, LongPoll} from "../js/phoenix"
let socket
describe("constructor", () => {
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
it("sets defaults", () => {
socket = new Socket("/socket")
assert.equal(socket.channels.length, 0)
assert.equal(socket.sendBuffer.length, 0)
assert.equal(socket.ref, 0)
assert.equal(socket.endPoint, "/socket/websocket")
assert.deepEqual(socket.stateChangeCallbacks, {open: [], close: [], error: [], message: []})
assert.equal(socket.transport, LongPoll)
assert.equal(socket.timeout, 10000)
assert.equal(socket.longpollerTimeout, 20000)
assert.equal(socket.heartbeatIntervalMs, 30000)
assert.equal(socket.logger, null)
assert.equal(typeof socket.reconnectAfterMs, "function")
})
it("supports closure or literal params", () => {
socket = new Socket("/socket", {params: {one: "two"}})
assert.deepEqual(socket.params(), {one: "two"})
socket = new Socket("/socket", {params: function(){ return({three: "four"}) }})
assert.deepEqual(socket.params(), {three: "four"})
})
it("overrides some defaults with options", () => {
const customTransport = function transport() {}
const customLogger = function logger() {}
const customReconnect = function reconnect() {}
socket = new Socket("/socket", {
timeout: 40000,
longpollerTimeout: 50000,
heartbeatIntervalMs: 60000,
transport: customTransport,
logger: customLogger,
reconnectAfterMs: customReconnect,
params: {one: "two"},
})
assert.equal(socket.timeout, 40000)
assert.equal(socket.longpollerTimeout, 50000)
assert.equal(socket.heartbeatIntervalMs, 60000)
assert.equal(socket.transport, customTransport)
assert.equal(socket.logger, customLogger)
assert.equal(socket.reconnectAfterMs, customReconnect)
assert.deepEqual(socket.params(), {one: "two"})
})
describe("with Websocket", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer("wss://example.com/")
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
it("defaults to Websocket transport if available", () => {
socket = new Socket("/socket")
assert.equal(socket.transport, WebSocket)
})
})
})
describe("protocol", () => {
beforeEach(() => {
socket = new Socket("/socket")
})
it("returns wss when location.protocol is https", () => {
jsdom.changeURL(window, "https://example.com/");
assert.equal(socket.protocol(), "wss")
})
it("returns ws when location.protocol is http", () => {
jsdom.changeURL(window, "http://example.com/");
assert.equal(socket.protocol(), "ws")
})
})
describe("endpointURL", () => {
it("returns endpoint for given full url", () => {
jsdom.changeURL(window, "https://example.com/");
socket = new Socket("wss://example.org/chat")
assert.equal(socket.endPointURL(), "wss://example.org/chat/websocket?vsn=2.0.0")
})
it("returns endpoint for given protocol-relative url", () => {
jsdom.changeURL(window, "https://example.com/");
socket = new Socket("//example.org/chat")
assert.equal(socket.endPointURL(), "wss://example.org/chat/websocket?vsn=2.0.0")
})
it("returns endpoint for given path on https host", () => {
jsdom.changeURL(window, "https://example.com/");
socket = new Socket("/socket")
assert.equal(socket.endPointURL(), "wss://example.com/socket/websocket?vsn=2.0.0")
})
it("returns endpoint for given path on http host", () => {
jsdom.changeURL(window, "http://example.com/");
socket = new Socket("/socket")
assert.equal(socket.endPointURL(), "ws://example.com/socket/websocket?vsn=2.0.0")
})
})
describe("connect with WebSocket", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer("wss://example.com/")
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket")
})
it("establishes websocket connection with endpoint", () => {
socket.connect()
let conn = socket.conn
assert.ok(conn instanceof WebSocket)
assert.equal(conn.url, socket.endPointURL())
})
it("sets callbacks for connection", () => {
let opens = 0
socket.onOpen(() => ++opens)
let closes = 0
socket.onClose(() => ++closes)
let lastError
socket.onError((error) => lastError = error)
let lastMessage
socket.onMessage((message) => lastMessage = message.payload)
socket.connect()
socket.conn.onopen[0]()
assert.equal(opens, 1)
socket.conn.onclose[0]()
assert.equal(closes, 1)
socket.conn.onerror[0]("error")
assert.equal(lastError, "error")
const data = {"topic":"topic","event":"event","payload":"payload","status":"ok"}
socket.conn.onmessage[0]({data: encode(data)})
assert.equal(lastMessage, "payload")
})
it("is idempotent", () => {
socket.connect()
let conn = socket.conn
socket.connect()
assert.deepStrictEqual(conn, socket.conn)
})
})
describe("connect with long poll", () => {
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
beforeEach(() => {
socket = new Socket("/socket")
})
it("establishes long poll connection with endpoint", () => {
socket.connect()
let conn = socket.conn
assert.ok(conn instanceof LongPoll)
assert.equal(conn.pollEndpoint, "http://example.com/socket/longpoll?vsn=2.0.0")
assert.equal(conn.timeout, 20000)
})
it("sets callbacks for connection", () => {
let opens = 0
socket.onOpen(() => ++opens)
let closes = 0
socket.onClose(() => ++closes)
let lastError
socket.onError((error) => lastError = error)
let lastMessage
socket.onMessage((message) => lastMessage = message.payload)
socket.connect()
socket.conn.onopen()
assert.equal(opens, 1)
socket.conn.onclose()
assert.equal(closes, 1)
socket.conn.onerror("error")
assert.equal(lastError, "error")
socket.connect()
const data = {"topic":"topic","event":"event","payload":"payload","status":"ok"}
socket.conn.onmessage({data: encode(data)})
assert.equal(lastMessage, "payload")
})
it("is idempotent", () => {
socket.connect()
let conn = socket.conn
socket.connect()
assert.deepStrictEqual(conn, socket.conn)
})
})
describe("disconnect", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer('wss://example.com/')
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket")
})
it("removes existing connection", () => {
socket.connect()
socket.disconnect()
assert.equal(socket.conn, null)
})
it("calls callback", () => {
let count = 0
socket.connect()
socket.disconnect(() => count++)
assert.equal(count, 1)
})
it("calls connection close callback", () => {
socket.connect()
const spy = sinon.spy(socket.conn, "close")
socket.disconnect(null, "code", "reason")
assert(spy.calledWith("code", "reason"))
})
it("does not throw when no connection", () => {
assert.doesNotThrow(() => {
socket.disconnect()
})
})
})
describe("connectionState", () => {
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
beforeEach(() => {
socket = new Socket("/socket")
})
it("defaults to closed", () => {
assert.equal(socket.connectionState(), "closed")
})
it("returns closed if readyState unrecognized", () => {
socket.connect()
socket.conn.readyState = 5678
assert.equal(socket.connectionState(), "closed")
})
it("returns connecting", () => {
socket.connect()
socket.conn.readyState = 0
assert.equal(socket.connectionState(), "connecting")
assert.ok(!socket.isConnected(), "is not connected")
})
it("returns open", () => {
socket.connect()
socket.conn.readyState = 1
assert.equal(socket.connectionState(), "open")
assert.ok(socket.isConnected(), "is connected")
})
it("returns closing", () => {
socket.connect()
socket.conn.readyState = 2
assert.equal(socket.connectionState(), "closing")
assert.ok(!socket.isConnected(), "is not connected")
})
it("returns closed", () => {
socket.connect()
socket.conn.readyState = 3
assert.equal(socket.connectionState(), "closed")
assert.ok(!socket.isConnected(), "is not connected")
})
})
describe("channel", () => {
let channel
beforeEach(() => {
socket = new Socket("/socket")
})
it("returns channel with given topic and params", () => {
channel = socket.channel("topic", {one: "two"})
assert.deepStrictEqual(channel.socket, socket)
assert.equal(channel.topic, "topic")
assert.deepEqual(channel.params(), {one: "two"})
})
it("adds channel to sockets channels list", () => {
assert.equal(socket.channels.length, 0)
channel = socket.channel("topic", {one: "two"})
assert.equal(socket.channels.length, 1)
const [foundChannel] = socket.channels
assert.deepStrictEqual(foundChannel, channel)
})
})
describe("remove", () => {
beforeEach(() => {
socket = new Socket("/socket")
})
it("removes given channel from channels", () => {
const channel1 = socket.channel("topic-1")
const channel2 = socket.channel("topic-2")
sinon.stub(channel1, "joinRef").returns(1)
sinon.stub(channel2, "joinRef").returns(2)
socket.remove(channel1)
assert.equal(socket.channels.length, 1)
const [foundChannel] = socket.channels
assert.deepStrictEqual(foundChannel, channel2)
})
})
describe("push", () => {
const data = {topic: "topic", event: "event", payload: "payload", ref: "ref"}
const json = encode(data)
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
beforeEach(() => {
socket = new Socket("/socket")
})
it("sends data to connection when connected", () => {
socket.connect()
socket.conn.readyState = 1 // open
const spy = sinon.spy(socket.conn, "send")
socket.push(data)
assert.ok(spy.calledWith(json))
})
it("buffers data when not connected", () => {
socket.connect()
socket.conn.readyState = 0 // connecting
const spy = sinon.spy(socket.conn, "send")
assert.equal(socket.sendBuffer.length, 0)
socket.push(data)
assert.ok(spy.neverCalledWith(json))
assert.equal(socket.sendBuffer.length, 1)
const [callback] = socket.sendBuffer
callback()
assert.ok(spy.calledWith(json))
})
})
describe("makeRef", () => {
beforeEach(() => {
socket = new Socket("/socket")
})
it("returns next message ref", () => {
assert.strictEqual(socket.ref, 0)
assert.strictEqual(socket.makeRef(), "1")
assert.strictEqual(socket.ref, 1)
assert.strictEqual(socket.makeRef(), "2")
assert.strictEqual(socket.ref, 2)
})
it("restarts for overflow", () => {
socket.ref = Number.MAX_SAFE_INTEGER + 1
assert.strictEqual(socket.makeRef(), "0")
assert.strictEqual(socket.ref, 0)
})
})
describe("sendHeartbeat", () => {
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
beforeEach(() => {
socket = new Socket("/socket")
socket.connect()
})
it("closes socket when heartbeat is not ack'd within heartbeat window", () => {
let closed = false
socket.conn.readyState = 1 // open
socket.conn.onclose = () => closed = true
socket.sendHeartbeat()
assert.equal(closed, false)
socket.sendHeartbeat()
assert.equal(closed, true)
})
it("pushes heartbeat data when connected", () => {
socket.conn.readyState = 1 // open
const spy = sinon.spy(socket.conn, "send")
const data = "[null,\"1\",\"phoenix\",\"heartbeat\",{}]"
socket.sendHeartbeat()
assert.ok(spy.calledWith(data))
})
it("no ops when not connected", () => {
socket.conn.readyState = 0 // connecting
const spy = sinon.spy(socket.conn, "send")
const data = encode({topic: "phoenix", event: "heartbeat", payload: {},ref: "1"})
socket.sendHeartbeat()
assert.ok(spy.neverCalledWith(data))
})
})
describe("flushSendBuffer", () => {
before(() => {
window.XMLHttpRequest = sinon.useFakeXMLHttpRequest()
})
after(() => {
window.XMLHttpRequest = null
})
beforeEach(() => {
socket = new Socket("/socket")
socket.connect()
})
it("calls callbacks in buffer when connected", () => {
socket.conn.readyState = 1 // open
const spy1 = sinon.spy()
const spy2 = sinon.spy()
const spy3 = sinon.spy()
socket.sendBuffer.push(spy1)
socket.sendBuffer.push(spy2)
socket.flushSendBuffer()
assert.ok(spy1.calledOnce)
assert.ok(spy2.calledOnce)
assert.equal(spy3.callCount, 0)
})
it("empties sendBuffer", () => {
socket.conn.readyState = 1 // open
socket.sendBuffer.push(() => {})
socket.flushSendBuffer()
assert.deepEqual(socket.sendBuffer.length, 0)
})
})
describe("onConnOpen", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer('wss://example.com/')
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket", {
reconnectAfterMs: () => 100000
})
socket.connect()
})
it("flushes the send buffer", () => {
socket.conn.readyState = 1 // open
const spy = sinon.spy()
socket.sendBuffer.push(spy)
socket.onConnOpen()
assert.ok(spy.calledOnce)
})
it("resets reconnectTimer", () => {
const spy = sinon.spy(socket.reconnectTimer, "reset")
socket.onConnOpen()
assert.ok(spy.calledOnce)
})
it("triggers onOpen callback", () => {
const spy = sinon.spy()
socket.onOpen(spy)
socket.onConnOpen()
assert.ok(spy.calledOnce)
})
})
describe("onConnClose", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer('wss://example.com/')
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket", {
reconnectAfterMs: () => 100000
})
socket.connect()
})
it('does not schedules reconnectTimer timeout if normal close', () => {
const spy = sinon.spy(socket.reconnectTimer, 'scheduleTimeout')
const event = { code: 1000 }
socket.onConnClose(event)
assert.ok(spy.notCalled)
})
it('schedules reconnectTimer timeout if not normal close', () => {
const spy = sinon.spy(socket.reconnectTimer, 'scheduleTimeout')
const event = { code: 1001 }
socket.onConnClose(event)
assert.ok(spy.calledOnce)
})
it("triggers onClose callback", () => {
const spy = sinon.spy()
socket.onClose(spy)
socket.onConnClose("event")
assert.ok(spy.calledWith("event"))
})
it("triggers channel error", () => {
const channel = socket.channel("topic")
const spy = sinon.spy(channel, "trigger")
socket.onConnClose()
assert.ok(spy.calledWith("phx_error"))
})
})
describe("onConnError", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer('wss://example.com/')
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket", {
reconnectAfterMs: () => 100000
})
socket.connect()
})
it("triggers onClose callback", () => {
const spy = sinon.spy()
socket.onError(spy)
socket.onConnError("error")
assert.ok(spy.calledWith("error"))
})
it("triggers channel error", () => {
const channel = socket.channel("topic")
const spy = sinon.spy(channel, "trigger")
socket.onConnError("error")
assert.ok(spy.calledWith("phx_error"))
})
})
describe("onConnMessage", () => {
let mockServer
before(() => {
mockServer = new WebSocketServer('wss://example.com/')
})
after((done) => {
mockServer.stop(() => {
window.WebSocket = null
done()
})
})
beforeEach(() => {
socket = new Socket("/socket", {
reconnectAfterMs: () => 100000
})
socket.connect()
})
it("parses raw message and triggers channel event", () => {
const message = encode({topic: "topic", event: "event", payload: "payload", ref: "ref"})
const data = {data: message}
const targetChannel = socket.channel("topic")
const otherChannel = socket.channel("off-topic")
const targetSpy = sinon.spy(targetChannel, "trigger")
const otherSpy = sinon.spy(otherChannel, "trigger")
socket.onConnMessage(data)
assert.ok(targetSpy.calledWith("event", "payload", "ref"))
assert.equal(targetSpy.callCount, 1)
assert.equal(otherSpy.callCount, 0)
})
it("triggers onMessage callback", () => {
const message = {"topic":"topic","event":"event","payload":"payload","ref":"ref"}
const spy = sinon.spy()
socket.onMessage(spy)
socket.onConnMessage({data: encode(message)})
assert.ok(spy.calledWith({
"topic": "topic",
"event": "event",
"payload": "payload",
"ref": "ref",
"join_ref": null
}))
})
})
describe("custom encoder and decoder", () => {
it("encodes to JSON array by default", () => {
socket = new Socket("/socket")
let payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}}
socket.encode(payload, encoded => {
assert.deepStrictEqual(encoded, '["1","2","topic","join",{"foo":"bar"}]')
})
})
it("allows custom encoding when using WebSocket transport", () => {
let encoder = (payload, callback) => callback("encode works")
socket = new Socket("/socket", {transport: WebSocket, encode: encoder})
socket.encode({foo: "bar"}, encoded => {
assert.deepStrictEqual(encoded, "encode works")
})
})
it("forces JSON encoding when using LongPoll transport", () => {
let encoder = (payload, callback) => callback("encode works")
socket = new Socket("/socket", {transport: LongPoll, encode: encoder})
let payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}}
socket.encode(payload, encoded => {
assert.deepStrictEqual(encoded, '["1","2","topic","join",{"foo":"bar"}]')
})
})
it("decodes JSON by default", () => {
socket = new Socket("/socket")
let encoded = '["1","2","topic","join",{"foo":"bar"}]'
socket.decode(encoded, decoded => {
assert.deepStrictEqual(decoded, {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}})
})
})
it("allows custom decoding when using WebSocket transport", () => {
let decoder = (payload, callback) => callback("decode works")
socket = new Socket("/socket", {transport: WebSocket, decode: decoder})
socket.decode("...esoteric format...", decoded => {
assert.deepStrictEqual(decoded, "decode works")
})
})
it("forces JSON decoding when using LongPoll transport", () => {
let decoder = (payload, callback) => callback("decode works")
socket = new Socket("/socket", {transport: LongPoll, decode: decoder})
let payload = {topic: "topic", ref: "2", join_ref: "1", event: "join", payload: {foo: "bar"}}
socket.decode('["1","2","topic","join",{"foo":"bar"}]', decoded => {
assert.deepStrictEqual(decoded, payload)
})
})
})