)!
var workItemSetCount: Int = 0
var workItemDidGetSet: Bool { return workItemSetCount > 0 }
override var workItem: DispatchWorkItem? {
didSet { workItemSetCount += 1 }
}
override var tries: Int {
get { return underlyingTries }
set(value) { underlyingTries = value }
}
var underlyingTries: (Int)!
override var queue: TimerQueue {
get { return underlyingQueue }
set(value) { underlyingQueue = value }
}
var underlyingQueue: (TimerQueue)!
//MARK: - reset
var resetCallsCount = 0
var resetCalled: Bool {
return resetCallsCount > 0
}
var resetClosure: (() -> Void)?
override func reset() {
resetCallsCount += 1
resetClosure?()
}
//MARK: - scheduleTimeout
var scheduleTimeoutCallsCount = 0
var scheduleTimeoutCalled: Bool {
return scheduleTimeoutCallsCount > 0
}
var scheduleTimeoutClosure: (() -> Void)?
override func scheduleTimeout() {
scheduleTimeoutCallsCount += 1
scheduleTimeoutClosure?()
}
}
================================================
FILE: Tests/Mocks/MockableProtocol.generated.swift
================================================
// Generated using Sourcery 1.0.2 — https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
// swiftlint:disable line_length
// swiftlint:disable variable_name
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
@testable import SwiftPhoenixClient
class PhoenixTransportMock: PhoenixTransport {
var readyState: PhoenixTransportReadyState {
get { return underlyingReadyState }
set(value) { underlyingReadyState = value }
}
var underlyingReadyState: PhoenixTransportReadyState!
var delegate: PhoenixTransportDelegate?
//MARK: - connect
var connectCallsCount = 0
var connectCalled: Bool {
return connectCallsCount > 0
}
var connectClosure: (() -> Void)?
func connect() {
connectCallsCount += 1
connectClosure?()
}
//MARK: - disconnect
var disconnectCodeReasonCallsCount = 0
var disconnectCodeReasonCalled: Bool {
return disconnectCodeReasonCallsCount > 0
}
var disconnectCodeReasonReceivedArguments: (code: Int, reason: String?)?
var disconnectCodeReasonReceivedInvocations: [(code: Int, reason: String?)] = []
var disconnectCodeReasonClosure: ((Int, String?) -> Void)?
func disconnect(code: Int, reason: String?) {
disconnectCodeReasonCallsCount += 1
disconnectCodeReasonReceivedArguments = (code: code, reason: reason)
disconnectCodeReasonReceivedInvocations.append((code: code, reason: reason))
disconnectCodeReasonClosure?(code, reason)
}
//MARK: - send
var sendDataCallsCount = 0
var sendDataCalled: Bool {
return sendDataCallsCount > 0
}
var sendDataReceivedData: Data?
var sendDataReceivedInvocations: [Data] = []
var sendDataClosure: ((Data) -> Void)?
func send(data: Data) {
sendDataCallsCount += 1
sendDataReceivedData = data
sendDataReceivedInvocations.append(data)
sendDataClosure?(data)
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/ChannelSpec.swift
================================================
//
// ChannelSpec.swift
// SwiftPhoenixClient
//
// Created by Daniel Rees on 5/18/18.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class ChannelSpec: QuickSpec {
override func spec() {
// Mocks
var mockClient: PhoenixTransportMock!
var mockSocket: SocketMock!
// Constants
let kDefaultRef = "1"
let kDefaultTimeout: TimeInterval = 10.0
// Clock
var fakeClock: FakeTimerQueue!
// UUT
var channel: Channel!
/// Utility method to easily filter the bindings for a channel by their event
func getBindings(_ event: String) -> [Binding]? {
return channel.syncBindingsDel.filter({ $0.event == event })
}
beforeEach {
// Any TimeoutTimer that is created will receive the fake clock
// when scheduling work items
fakeClock = FakeTimerQueue()
TimerQueue.main = fakeClock
mockClient = PhoenixTransportMock()
mockSocket = SocketMock(endPoint: "/socket", transport: { _ in mockClient })
mockSocket.connection = mockClient
mockSocket.timeout = kDefaultTimeout
mockSocket.makeRefReturnValue = kDefaultRef
mockSocket.reconnectAfter = { tries -> TimeInterval in
return tries > 3 ? 10 : [1, 2, 5, 10][tries - 1]
}
mockSocket.rejoinAfter = Defaults.rejoinSteppedBackOff
channel = Channel(topic: "topic", params: ["one": "two"], socket: mockSocket)
mockSocket.channelParamsReturnValue = channel
}
afterEach {
fakeClock.reset()
}
describe("constructor") {
it("sets defaults", closure: {
channel = Channel(topic: "topic", params: ["one": "two"], socket: mockSocket)
expect(channel.state).to(equal(ChannelState.closed))
expect(channel.topic).to(equal("topic"))
expect(channel.params["one"] as? String).to(equal("two"))
expect(channel.socket === mockSocket).to(beTrue())
expect(channel.timeout).to(equal(10))
expect(channel.joinedOnce).to(beFalse())
expect(channel.joinPush).toNot(beNil())
expect(channel.pushBuffer).to(beEmpty())
})
it("sets up joinPush with literal params", closure: {
channel = Channel(topic: "topic", params: ["one": "two"], socket: mockSocket)
let joinPush = channel.joinPush
expect(joinPush?.channel === channel).to(beTrue())
expect(joinPush?.payload["one"] as? String).to(equal("two"))
expect(joinPush?.event).to(equal("phx_join"))
expect(joinPush?.timeout).to(equal(10))
})
it("should not introduce any retain cycles", closure: {
weak var weakChannel = Channel(topic: "topic",
params: ["one": 2],
socket: mockSocket)
expect(weakChannel).to(beNil())
})
}
describe("onMessage") {
it("returns message by default", closure: {
let message = channel.onMessage(Message(ref: "original"))
expect(message.ref).to(equal("original"))
})
it("can be overridden", closure: {
channel.onMessage = { message in
return Message(ref: "modified")
}
let message = channel.onMessage(Message(ref: "original"))
expect(message.ref).to(equal("modified"))
})
}
describe("updating join params") {
it("can update join params", closure: {
let params: Payload = ["value": 1]
let change: Payload = ["value": 2]
channel = Channel(topic: "topic", params: params, socket: mockSocket)
let joinPush = channel.joinPush
expect(joinPush?.channel === channel).to(beTrue())
expect(joinPush?.payload["value"] as? Int).to(equal(1))
expect(joinPush?.event).to(equal(ChannelEvent.join))
expect(joinPush?.timeout).to(equal(10))
channel.params = change
expect(joinPush?.channel === channel).to(beTrue())
expect(joinPush?.payload["value"] as? Int).to(equal(2))
expect(channel?.params["value"] as? Int).to(equal(2))
expect(joinPush?.event).to(equal(ChannelEvent.join))
expect(joinPush?.timeout).to(equal(10))
})
}
describe("join") {
it("sets state to joining", closure: {
channel.join()
expect(channel.state.rawValue).to(equal("joining"))
})
it("sets joinedOnce to true", closure: {
expect(channel.joinedOnce).to(beFalse())
channel.join()
expect(channel.joinedOnce).to(beTrue())
})
it("throws if attempting to join multiple times", closure: {
channel.join()
// Method is not marked to throw
expect { channel.join() }.to(throwAssertion())
})
it("triggers socket push with channel params", closure: {
channel.join()
expect(mockSocket.pushTopicEventPayloadRefJoinRefCalled).to(beTrue())
let args = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args?.topic).to(equal("topic"))
expect(args?.event).to(equal("phx_join"))
expect(args?.payload["one"] as? String).to(equal("two"))
expect(args?.ref).to(equal(kDefaultRef))
expect(args?.joinRef).to(equal(channel.joinRef))
})
it("can set timeout on joinPush", closure: {
let newTimeout: TimeInterval = 2.0
let joinPush = channel.joinPush
expect(joinPush?.timeout).to(equal(kDefaultTimeout))
let _ = channel.join(timeout: newTimeout)
expect(joinPush?.timeout).to(equal(newTimeout))
})
it("leaves existing duplicate topic on new join") {
let transport: ((URL) -> PhoenixTransport) = { _ in return mockClient }
let spySocket = SocketSpy(endPoint: "/socket", transport: transport)
let channel = spySocket.channel("topic", params: ["one": "two"])
mockClient.readyState = .open
spySocket.onConnectionOpen()
channel.join()
.receive("ok") { (message) in
let newChannel = spySocket.channel("topic")
expect(channel.isJoined).to(beTrue())
newChannel.join()
expect(channel.isJoined).to(beFalse())
}
channel.joinPush.trigger("ok", payload: [:])
}
}
describe("timeout behavior") {
var spySocket: SocketSpy!
var joinPush: Push!
var timeout: TimeInterval!
func receiveSocketOpen() {
mockClient.readyState = .open
spySocket.onConnectionOpen()
}
beforeEach {
mockClient.readyState = .closed
let transport: ((URL) -> PhoenixTransport) = { _ in return mockClient }
spySocket = SocketSpy(endPoint: "/socket", transport: transport)
channel = Channel(topic: "topic", params: ["one": "two"], socket: spySocket)
joinPush = channel.joinPush
timeout = joinPush.timeout
}
it("succeeds before timeout", closure: {
spySocket.connect()
receiveSocketOpen()
channel.join()
expect(spySocket.pushCalled).to(beTrue())
expect(channel.timeout).to(equal(10.0))
fakeClock.tick(0.100)
joinPush.trigger("ok", payload: [:])
expect(channel.state).to(equal(.joined))
fakeClock.tick(timeout)
expect(spySocket.pushCallCount).to(equal(1))
})
it("retries with backoff after timeout", closure: {
spySocket.connect()
receiveSocketOpen()
var timeoutCallCount = 0
channel.join().receive("timeout", callback: { (_) in
timeoutCallCount += 1
})
expect(spySocket.pushCallCount).to(equal(1))
expect(spySocket.pushArgs[1]?.event).to(equal("phx_join"))
expect(timeoutCallCount).to(equal(0))
fakeClock.tick(timeout) // leave pushed to server
expect(spySocket.pushCallCount).to(equal(2))
expect(spySocket.pushArgs[2]?.event).to(equal("phx_leave"))
expect(timeoutCallCount).to(equal(1))
fakeClock.tick(timeout + 1) // rejoin
expect(spySocket.pushCallCount).to(equal(4))
expect(spySocket.pushArgs[3]?.event).to(equal("phx_join"))
expect(spySocket.pushArgs[4]?.event).to(equal("phx_leave"))
expect(timeoutCallCount).to(equal(2))
fakeClock.tick(10)
joinPush.trigger("ok", payload: [:])
expect(spySocket.pushCallCount).to(equal(5))
expect(spySocket.pushArgs[5]?.event).to(equal("phx_join"))
expect(channel.state).to(equal(.joined))
})
it("with socket and join delay", closure: {
channel.join()
expect(spySocket.pushCallCount).to(equal(1))
// Open the socket after a delay
fakeClock.tick(9.0)
expect(spySocket.pushCallCount).to(equal(1))
// join request returns between timeouts
fakeClock.tick(1.0)
spySocket.connect()
expect(channel.state).to(equal(.errored))
receiveSocketOpen()
joinPush.trigger("ok", payload: [:])
fakeClock.tick(1.0)
expect(channel.state).to(equal(.joined))
expect(spySocket.pushCallCount).to(equal(3))
})
it("with socket delay only", closure: {
channel.join()
expect(channel.state).to(equal(.joining))
// connect socket after a delay
fakeClock.tick(6.0)
spySocket.connect()
// open Socket after delay
fakeClock.tick(5.0)
receiveSocketOpen()
joinPush.trigger("ok", payload: [:])
joinPush.trigger("ok", payload: [:])
expect(channel.state).to(equal(.joined))
})
}
describe("joinPush") {
var spySocket: SocketSpy!
var joinPush: Push!
beforeEach {
mockClient.readyState = .open
spySocket = SocketSpy(endPoint: "/socket",
transport: { _ in return mockClient })
spySocket.connect()
channel = Channel(topic: "topic", params: ["one": "two"], socket: spySocket)
joinPush = channel.joinPush
channel.join()
}
func receivesOk() {
fakeClock.tick(joinPush.timeout / 2)
joinPush.trigger("ok", payload: ["a": "b"])
}
func receivesTimeout() {
fakeClock.tick(joinPush.timeout * 2)
}
func receiveError() {
fakeClock.tick(joinPush.timeout / 2)
joinPush.trigger("error", payload: ["a": "b"])
}
describe("receives 'ok'", {
it("sets channel state to joined", closure: {
expect(channel.state).toNot(equal(.joined))
receivesOk()
expect(channel.state).to(equal(.joined))
})
it("triggers receive(ok) callback after ok response", closure: {
var callbackCallCount: Int = 0
joinPush.receive("ok", callback: {_ in callbackCallCount += 1})
receivesOk()
expect(callbackCallCount).to(equal(1))
})
it("triggers receive('ok') callback if ok response already received", closure: {
receivesOk()
var callbackCallCount: Int = 0
joinPush.receive("ok", callback: {_ in callbackCallCount += 1})
expect(callbackCallCount).to(equal(1))
})
it("does not trigger other receive callbacks after ok response", closure: {
var callbackCallCount: Int = 0
joinPush
.receive("error", callback: {_ in callbackCallCount += 1})
.receive("timeout", callback: {_ in callbackCallCount += 1})
receivesOk()
receivesTimeout()
expect(callbackCallCount).to(equal(0))
})
it("clears timeoutTimer workItem", closure: {
expect(joinPush.timeoutWorkItem).toNot(beNil())
receivesOk()
expect(joinPush.timeoutWorkItem).to(beNil())
})
it("sets receivedMessage", closure: {
expect(joinPush.receivedMessage).to(beNil())
receivesOk()
expect(joinPush.receivedMessage).toNot(beNil())
expect(joinPush.receivedMessage?.status).to(equal("ok"))
expect(joinPush.receivedMessage?.payload["a"] as? String).to(equal("b"))
})
it("removes channel binding", closure: {
var bindings = getBindings("chan_reply_3")
expect(bindings).to(haveCount(1))
receivesOk()
bindings = getBindings("chan_reply_3")
expect(bindings).to(haveCount(0))
})
it("sets channel state to joined", closure: {
receivesOk()
expect(channel.state).to(equal(.joined))
})
it("resets channel rejoinTimer", closure: {
let mockRejoinTimer = TimeoutTimerMock()
channel.rejoinTimer = mockRejoinTimer
receivesOk()
expect(mockRejoinTimer.resetCallsCount).to(equal(1))
})
it("sends and empties channel's buffered pushEvents", closure: {
let mockPush = PushMock(channel: channel, event: "new:msg")
channel.pushBuffer.append(mockPush)
receivesOk()
expect(mockPush.sendCalled).to(beTrue())
expect(channel.pushBuffer).to(haveCount(0))
})
})
describe("receives 'timeout'", {
it("sets channel state to errored", closure: {
var timeoutReceived = false
joinPush.receive("timeout", callback: { (_) in
timeoutReceived = true
expect(channel.state).to(equal(.errored))
})
receivesTimeout()
expect(timeoutReceived).to(beTrue())
})
it("triggers receive('timeout') callback after ok response", closure: {
var receiveTimeoutCallCount = 0
joinPush.receive("timeout", callback: { (_) in
receiveTimeoutCallCount += 1
})
receivesTimeout()
expect(receiveTimeoutCallCount).to(equal(1))
})
it("does not trigger other receive callbacks after timeout response", closure: {
var receiveOkCallCount = 0
var receiveErrorCallCount = 0
var timeoutReceived = false
joinPush
.receive("ok") {_ in receiveOkCallCount += 1 }
.receive("error") {_ in receiveErrorCallCount += 1 }
.receive("timeout", callback: { (_) in
expect(receiveOkCallCount).to(equal(0))
expect(receiveErrorCallCount).to(equal(0))
timeoutReceived = true
})
receivesTimeout()
receivesOk()
expect(timeoutReceived).to(beTrue())
})
it("schedules rejoinTimer timeout", closure: {
let mockRejoinTimer = TimeoutTimerMock()
channel.rejoinTimer = mockRejoinTimer
receivesTimeout()
expect(mockRejoinTimer.scheduleTimeoutCalled).to(beTrue())
})
})
describe("receives `error`", {
it("triggers receive('error') callback after error response", closure: {
expect(channel.state).to(equal(.joining))
var errorCallsCount = 0
joinPush.receive("error") { (_) in errorCallsCount += 1 }
receiveError()
joinPush.trigger("error", payload: [:])
expect(errorCallsCount).to(equal(1))
})
it("triggers receive('error') callback if error response already received", closure: {
receiveError()
var errorCallsCount = 0
joinPush.receive("error") { (_) in errorCallsCount += 1 }
expect(errorCallsCount).to(equal(1))
})
it("does not trigger other receive callbacks after ok response", closure: {
var receiveOkCallCount = 0
var receiveTimeoutCallCount = 0
var receiveErrorCallCount = 0
joinPush
.receive("ok") {_ in receiveOkCallCount += 1 }
.receive("error", callback: { (_) in
receiveErrorCallCount += 1
channel.leave()
})
.receive("timeout") {_ in receiveTimeoutCallCount += 1 }
receiveError()
receivesTimeout()
expect(receiveErrorCallCount).to(equal(1))
expect(receiveOkCallCount).to(equal(0))
expect(receiveTimeoutCallCount).to(equal(0))
})
it("clears timeoutTimer workItem", closure: {
expect(joinPush.timeoutWorkItem).toNot(beNil())
receiveError()
expect(joinPush.timeoutWorkItem).to(beNil())
})
it("sets receivedMessage", closure: {
expect(joinPush.receivedMessage).to(beNil())
receiveError()
expect(joinPush.receivedMessage).toNot(beNil())
expect(joinPush.receivedMessage?.status).to(equal("error"))
expect(joinPush.receivedMessage?.payload["a"] as? String).to(equal("b"))
})
it("removes channel binding", closure: {
var bindings = getBindings("chan_reply_3")
expect(bindings).to(haveCount(1))
receiveError()
bindings = getBindings("chan_reply_3")
expect(bindings).to(haveCount(0))
})
it("does not sets channel state to joined", closure: {
receiveError()
expect(channel.state).toNot(equal(.joined))
})
it("does not trigger channel's buffered pushEvents", closure: {
let mockPush = PushMock(channel: channel, event: "new:msg")
channel.pushBuffer.append(mockPush)
receiveError()
expect(mockPush.sendCalled).to(beFalse())
expect(channel.pushBuffer).to(haveCount(1))
})
})
}
describe("onError") {
var spySocket: SocketSpy!
var joinPush: Push!
beforeEach {
mockClient.readyState = .open
spySocket = SocketSpy(endPoint: "/socket",
transport: { _ in return mockClient })
spySocket.connect()
channel = Channel(topic: "topic", params: ["one": "two"], socket: spySocket)
joinPush = channel.joinPush
channel.join()
joinPush.trigger("ok", payload: [:])
}
it("does not trigger redundant errors during backoff", closure: {
// Spy the channel's Join Push
let mockPush = PushMock(channel: channel, event: "event")
channel.joinPush = mockPush
expect(mockPush.resendCalled).to(beFalse())
channel.trigger(event: ChannelEvent.error)
fakeClock.tick(1.0)
expect(mockPush.resendCalled).to(beTrue())
expect(mockPush.resendCallsCount).to(equal(1))
channel.trigger(event: "error")
fakeClock.tick(1.0)
expect(mockPush.resendCallsCount).to(equal(1))
})
describe("while joining") {
var mockPush: PushMock!
beforeEach {
channel = Channel(topic: "topic", params: ["one": "two"], socket: mockSocket)
// Spy the channel's Join Push
mockPush = PushMock(channel: channel, event: "event")
mockPush.ref = "10"
channel.joinPush = mockPush
channel.state = .joining
}
it("removes the joinPush message from send buffer") {
channel.trigger(event: ChannelEvent.error)
expect(mockSocket.removeFromSendBufferRefCalled).to(beTrue())
expect(mockSocket.removeFromSendBufferRefReceivedRef).to(equal("10"))
}
it("resets the joinPush") {
channel.trigger(event: ChannelEvent.error)
expect(mockPush.resetCalled).to(beTrue())
}
}
it("sets channel state to .errored", closure: {
expect(channel.state).toNot(equal(.errored))
channel.trigger(event: ChannelEvent.error)
expect(channel.state).to(equal(.errored))
})
it("tries to rejoin with backoff", closure: {
let mockRejoinTimer = TimeoutTimerMock()
channel.rejoinTimer = mockRejoinTimer
channel.trigger(event: ChannelEvent.error)
expect(mockRejoinTimer.scheduleTimeoutCalled).to(beTrue())
})
it("does not rejoin if channel leaving", closure: {
channel.state = .leaving
let mockPush = PushMock(channel: channel, event: "event")
channel.joinPush = mockPush
spySocket.onConnectionError(TestError.stub, response: nil)
fakeClock.tick(1.0)
expect(mockPush.sendCallsCount).to(equal(0))
fakeClock.tick(2.0)
expect(mockPush.sendCallsCount).to(equal(0))
expect(channel.state).to(equal(.leaving))
})
it("does nothing if channel is closed", closure: {
channel.state = .closed
let mockPush = PushMock(channel: channel, event: "event")
channel.joinPush = mockPush
spySocket.onConnectionError(TestError.stub, response: nil)
fakeClock.tick(1.0)
expect(mockPush.sendCallsCount).to(equal(0))
fakeClock.tick(2.0)
expect(mockPush.sendCallsCount).to(equal(0))
expect(channel.state).to(equal(.closed))
})
it("triggers additional callbacks", closure: {
var onErrorCallCount = 0
channel.onError({ (_) in onErrorCallCount += 1 })
joinPush.trigger("ok", payload: [:])
expect(channel.state).to(equal(.joined))
expect(onErrorCallCount).to(equal(0))
channel.trigger(event: ChannelEvent.error)
expect(onErrorCallCount).to(equal(1))
})
}
describe("onClose") {
beforeEach {
mockClient.readyState = .open
channel.join()
}
it("sets state to closed", closure: {
expect(channel.state).toNot(equal(.closed))
channel.trigger(event: ChannelEvent.close)
expect(channel.state).to(equal(.closed))
})
it("does not rejoin", closure: {
let mockJoinPush = PushMock(channel: channel, event: "phx_join")
channel.joinPush = mockJoinPush
channel.trigger(event: ChannelEvent.close)
fakeClock.tick(1.0)
expect(mockJoinPush.sendCalled).to(beFalse())
fakeClock.tick(2.0)
expect(mockJoinPush.sendCalled).to(beFalse())
})
it("resets the rejoin timer", closure: {
let mockRejoinTimer = TimeoutTimerMock()
channel.rejoinTimer = mockRejoinTimer
channel.trigger(event: ChannelEvent.close)
expect(mockRejoinTimer.resetCalled).to(beTrue())
})
it("removes self from socket", closure: {
channel.trigger(event: ChannelEvent.close)
expect(mockSocket.removeCalled).to(beTrue())
let removedChannel = mockSocket.removeReceivedChannel
expect(removedChannel === channel).to(beTrue())
})
it("triggers additional callbacks", closure: {
var onCloseCallCount = 0
channel.onClose({ (_) in
onCloseCallCount += 1
})
channel.trigger(event: ChannelEvent.close)
expect(onCloseCallCount).to(equal(1))
})
}
describe("canPush") {
it("returns true when socket connected and channel joined", closure: {
channel.state = .joined
mockClient.readyState = .open
expect(channel.canPush).to(beTrue())
})
it("otherwise returns false", closure: {
channel.state = .joined
mockClient.readyState = .closed
expect(channel.canPush).to(beFalse())
channel.state = .joining
mockClient.readyState = .open
expect(channel.canPush).to(beFalse())
channel.state = .joining
mockClient.readyState = .closed
expect(channel.canPush).to(beFalse())
})
}
describe("on") {
beforeEach {
mockSocket.makeRefClosure = nil
mockSocket.makeRefReturnValue = kDefaultRef
}
it("sets up callback for event", closure: {
var onCallCount = 0
channel.trigger(event: "event", ref: kDefaultRef)
expect(onCallCount).to(equal(0))
channel.on("event", callback: { (_) in
onCallCount += 1
})
channel.trigger(event: "event", ref: kDefaultRef)
expect(onCallCount).to(equal(1))
})
it("other event callbacks are ignored", closure: {
var onCallCount = 0
let ignoredOnCallCount = 0
channel.trigger(event: "event", ref: kDefaultRef)
expect(ignoredOnCallCount).to(equal(0))
channel.on("event", callback: { (_) in
onCallCount += 1
})
channel.trigger(event: "event", ref: kDefaultRef)
expect(ignoredOnCallCount).to(equal(0))
})
it("generates unique refs for callbacks ", closure: {
let ref1 = channel.on("event1", callback: { _ in })
let ref2 = channel.on("event2", callback: { _ in })
expect(ref1).toNot(equal(ref2))
expect(ref1 + 1).to(equal(ref2))
})
}
describe("off") {
beforeEach {
mockSocket.makeRefClosure = nil
mockSocket.makeRefReturnValue = kDefaultRef
}
it("removes all callbacks for event", closure: {
var callCount1 = 0
var callCount2 = 0
var callCount3 = 0
channel.on("event", callback: { _ in callCount1 += 1})
channel.on("event", callback: { _ in callCount2 += 1})
channel.on("other", callback: { _ in callCount3 += 1})
channel.off("event")
channel.trigger(event: "event", ref: kDefaultRef)
channel.trigger(event: "other", ref: kDefaultRef)
expect(callCount1).to(equal(0))
expect(callCount2).to(equal(0))
expect(callCount3).to(equal(1))
})
it("removes callback by ref", closure: {
var callCount1 = 0
var callCount2 = 0
let ref1 = channel.on("event", callback: { _ in callCount1 += 1})
let _ = channel.on("event", callback: { _ in callCount2 += 1})
channel.off("event", ref: ref1)
channel.trigger(event: "event", ref: kDefaultRef)
expect(callCount1).to(equal(0))
expect(callCount2).to(equal(1))
})
}
describe("push") {
beforeEach {
mockSocket.makeRefClosure = nil
mockSocket.makeRefReturnValue = kDefaultRef
mockClient.readyState = .open
}
it("sends push event when successfully joined", closure: {
channel.join().trigger("ok", payload: [:])
channel.push("event", payload: ["foo": "bar"])
expect(mockSocket.pushTopicEventPayloadRefJoinRefCalled).to(beTrue())
let args = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args?.topic).to(equal("topic"))
expect(args?.event).to(equal("event"))
expect(args?.payload["foo"] as? String).to(equal("bar"))
expect(args?.joinRef).to(equal(channel.joinRef))
expect(args?.ref).to(equal(kDefaultRef))
})
it("enqueues push event to be sent once join has succeeded", closure: {
let joinPush = channel.join()
channel.push("event", payload: ["foo": "bar"])
let args = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args?.payload["foo"]).to(beNil())
fakeClock.tick(channel.timeout / 2)
joinPush.trigger("ok", payload: [:])
expect(mockSocket.pushTopicEventPayloadRefJoinRefCalled).to(beTrue())
let args2 = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args2?.payload["foo"] as? String).to(equal("bar"))
})
it("does not push if channel join times out", closure: {
let joinPush = channel.join()
channel.push("event", payload: ["foo": "bar"])
let args = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args?.payload["foo"]).to(beNil())
fakeClock.tick(channel.timeout * 2)
joinPush.trigger("ok", payload: [:])
expect(mockSocket.pushTopicEventPayloadRefJoinRefCalled).to(beTrue())
let args2 = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args2?.payload["foo"]).to(beNil())
})
it("uses channel timeout by default", closure: {
channel.join().trigger("ok", payload: [:])
var timeoutCallsCount = 0
channel
.push("event", payload: ["foo": "bar"])
.receive("timeout", callback: { (_) in
timeoutCallsCount += 1
})
fakeClock.tick(channel.timeout / 2)
expect(timeoutCallsCount).to(equal(0))
fakeClock.tick(channel.timeout)
expect(timeoutCallsCount).to(equal(1))
})
it("accepts timeout arg", closure: {
channel.join().trigger("ok", payload: [:])
var timeoutCallsCount = 0
channel
.push("event", payload: ["foo": "bar"], timeout: channel.timeout * 2)
.receive("timeout", callback: { (_) in
timeoutCallsCount += 1
})
fakeClock.tick(channel.timeout)
expect(timeoutCallsCount).to(equal(0))
fakeClock.tick(channel.timeout * 2)
expect(timeoutCallsCount).to(equal(1))
})
it("does not time out after receiving 'ok'", closure: {
channel.join().trigger("ok", payload: [:])
var timeoutCallsCount = 0
let push = channel.push("event", payload: ["foo": "bar"])
push.receive("timeout", callback: { (_) in
timeoutCallsCount += 1
})
fakeClock.tick(channel.timeout / 2)
expect(timeoutCallsCount).to(equal(0))
push.trigger("ok", payload: [:])
fakeClock.tick(channel.timeout)
expect(timeoutCallsCount).to(equal(0))
})
it("throws if channel has not been joined", closure: {
expect { channel.push("event", payload: [:]) }.to(throwAssertion())
})
}
describe("leave") {
beforeEach {
mockClient.readyState = .open
channel.join().trigger("ok", payload: [:])
}
it("unsubscribes from server events", closure: {
mockSocket.makeRefClosure = nil
mockSocket.makeRefReturnValue = kDefaultRef
let joinRef = channel.joinRef
channel.leave()
expect(mockSocket.pushTopicEventPayloadRefJoinRefCalled).to(beTrue())
let args = mockSocket.pushTopicEventPayloadRefJoinRefReceivedArguments
expect(args?.topic).to(equal("topic"))
expect(args?.event).to(equal("phx_leave"))
expect(args?.payload).to(beEmpty())
expect(args?.joinRef).to(equal(joinRef))
expect(args?.ref).to(equal(kDefaultRef))
})
it("closes channel on 'ok' from server", closure: {
let socket = Socket(endPoint: "/socket", transport: { _ in return mockClient })
let channel = socket.channel("topic", params: ["one": "two"])
channel.join().trigger("ok", payload: [:])
let anotherChannel = socket.channel("another", params: ["three": "four"])
expect(socket.channels).to(haveCount(2))
channel.leave().trigger("ok", payload: [:])
expect(socket.channels).to(haveCount(1))
expect(socket.channels.first === anotherChannel).to(beTrue())
})
}
describe("isMemeber") {
it("returns false if the message topic does not match the channel") {
let message = Message(topic: "other")
expect(channel.isMember(message)).to(beFalse())
}
it("returns true if topics match but the message doesn't have a join ref") {
let message = Message(topic: "topic", event: ChannelEvent.close, joinRef: nil)
expect(channel.isMember(message)).to(beTrue())
}
it("returns true if topics and join refs match") {
channel.joinPush.ref = "2"
let message = Message(topic: "topic", event: ChannelEvent.close, joinRef: "2")
expect(channel.isMember(message)).to(beTrue())
}
it("returns true if topics and join refs match but event is not lifecycle") {
channel.joinPush.ref = "2"
let message = Message(topic: "topic", event: "event", joinRef: "2")
expect(channel.isMember(message)).to(beTrue())
}
it("returns false topics match and is a lifecycle event but join refs do not match ") {
channel.joinPush.ref = "2"
let message = Message(topic: "topic", event: ChannelEvent.close, joinRef: "1")
expect(channel.isMember(message)).to(beFalse())
}
}
describe("isClosed") {
it("returns true if state is .closed", closure: {
channel.state = .joined
expect(channel.isClosed).to(beFalse())
channel.state = .closed
expect(channel.isClosed).to(beTrue())
})
}
describe("isErrored") {
it("returns true if state is .errored", closure: {
channel.state = .joined
expect(channel.isErrored).to(beFalse())
channel.state = .errored
expect(channel.isErrored).to(beTrue())
})
}
describe("isJoined") {
it("returns true if state is .joined", closure: {
channel.state = .leaving
expect(channel.isJoined).to(beFalse())
channel.state = .joined
expect(channel.isJoined).to(beTrue())
})
}
describe("isJoining") {
it("returns true if state is .joining", closure: {
channel.state = .joined
expect(channel.isJoining).to(beFalse())
channel.state = .joining
expect(channel.isJoining).to(beTrue())
})
}
describe("isLeaving") {
it("returns true if state is .leaving", closure: {
channel.state = .joined
expect(channel.isLeaving).to(beFalse())
channel.state = .leaving
expect(channel.isLeaving).to(beTrue())
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/DefaultSerializerSpec.swift
================================================
//
// DefaultSerializerSpec.swift
// SwiftPhoenixClient
//
// Created by Daniel Rees on 1/17/19.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class DefaultSerializerSpec: QuickSpec {
override func spec() {
describe("encode and decode message") {
it("converts dictionary to Data and back to Message", closure: {
let body: [Any] = ["join_ref", "ref", "topic", "event", ["user_id": "abc123"]]
let data = Defaults.encode(body)
expect(String(data: data, encoding: .utf8)).to(equal("[\"join_ref\",\"ref\",\"topic\",\"event\",{\"user_id\":\"abc123\"}]"))
let json = Defaults.decode(data) as? [Any]
let message = Message(json: json!)
expect(message?.ref).to(equal("ref"))
expect(message?.joinRef).to(equal("join_ref"))
expect(message?.topic).to(equal("topic"))
expect(message?.event).to(equal("event"))
expect(message?.payload["user_id"] as? String).to(equal("abc123"))
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/HeartbeatTimerSpec.swift
================================================
//
// HeartbeatTimerSpec.swift
// SwiftPhoenixClientTests
//
// Created by Daniel Rees on 8/24/21.
// Copyright © 2021 SwiftPhoenixClient. All rights reserved.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class HeartbeatTimerSpec: QuickSpec {
override func spec() {
let queue = DispatchQueue(label: "heartbeat.timer.spec")
var timer: HeartbeatTimer!
beforeEach {
timer = HeartbeatTimer(timeInterval: 10, queue: queue)
}
describe("isValid") {
it("returns false if is not started") {
expect(timer.isValid).to(beFalse())
}
it("returns true if the timer has started") {
timer.start { /* no-op */ }
expect(timer.isValid).to(beTrue())
}
it("returns false if timer has been stopped") {
timer.start { /* no-op */ }
timer.stop()
expect(timer.isValid).to(beFalse())
}
}
describe("fire") {
it("calls the event handler") {
var timerCalled = 0
timer.start { timerCalled += 1 }
expect(timerCalled).to(equal(0))
timer.fire()
expect(timerCalled).to(equal(1))
}
it("does not call event handler if stopped") {
var timerCalled = 0
timer.start { timerCalled += 1 }
expect(timerCalled).to(equal(0))
timer.stop()
timer.fire()
expect(timerCalled).to(equal(0))
}
}
describe("equatable") {
it("equates different timers correctly", closure: {
let timerA = HeartbeatTimer(timeInterval: 10, queue: queue)
let timerB = HeartbeatTimer(timeInterval: 10, queue: queue)
expect(timerA).to(equal(timerA))
expect(timerA).toNot(equal(timerB))
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/MessageSpec.swift
================================================
//
// MessageSpec.swift
// SwiftPhoenixClientTests
//
// Created by Daniel Rees on 10/27/21.
// Copyright © 2021 SwiftPhoenixClient. All rights reserved.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class MessageSpec: QuickSpec {
override func spec() {
describe("json parsing") {
it("parses a normal message") {
let json: [Any] = ["2", "6", "my-topic", "update", ["user": "James S.", "message": "This is a test"]]
let message = Message(json: json)
expect(message?.ref).to(equal("6"))
expect(message?.joinRef).to(equal("2"))
expect(message?.topic).to(equal("my-topic"))
expect(message?.event).to(equal("update"))
expect(message?.payload["user"] as? String).to(equal("James S."))
expect(message?.payload["message"] as? String).to(equal("This is a test"))
expect(message?.status).to(beNil())
}
it("parses a reply") {
let json: [Any] = ["2", "6", "my-topic", "phx_reply", ["response": ["user": "James S.", "message": "This is a test"], "status": "ok"]]
let message = Message(json: json)
expect(message?.ref).to(equal("6"))
expect(message?.joinRef).to(equal("2"))
expect(message?.topic).to(equal("my-topic"))
expect(message?.event).to(equal("phx_reply"))
expect(message?.payload["user"] as? String).to(equal("James S."))
expect(message?.payload["message"] as? String).to(equal("This is a test"))
expect(message?.status).to(equal("ok"))
}
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/PresenceSpec.swift
================================================
//
// PresenceSpec.swift
// SwiftPhoenixClient
//
// Created by Simon Bergström on 2018-10-03.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class PresenceSpec: QuickSpec {
override func spec() {
/// Fixtures
let fixJoins: Presence.State = ["u1": ["metas": [["id":1, "phx_ref": "1.2"]]]]
let fixLeaves: Presence.State = ["u2": ["metas": [["id":2, "phx_ref": "2"]]]]
let fixState: Presence.State = [
"u1": ["metas": [["id":1, "phx_ref": "1"]]],
"u2": ["metas": [["id":2, "phx_ref": "2"]]],
"u3": ["metas": [["id":3, "phx_ref": "3"]]]
]
let listByFirst: (_ key: String, _ presence: Presence.Map) -> Presence.Meta
= { key, pres in
return pres["metas"]!.first!
}
/// Mocks
var mockSocket: SocketMock!
var channel: Channel!
/// UUT
var presence: Presence!
beforeEach {
let mockTransport = PhoenixTransportMock()
mockSocket = SocketMock(endPoint: "/socket", transport: { _ in mockTransport })
mockSocket.timeout = 10.0
mockSocket.makeRefReturnValue = "1"
mockSocket.reconnectAfter = { _ in return 1 }
channel = Channel(topic: "topic", params: [:], socket: mockSocket)
channel.joinPush.ref = "1"
presence = Presence(channel: channel)
}
describe("init") {
it("sets defaults", closure: {
expect(presence.state).to(beEmpty())
expect(presence.pendingDiffs).to(beEmpty())
expect(presence.channel === channel).to(beTrue())
expect(presence.joinRef).to(beNil())
})
it("binds to channel with default options", closure: {
expect(presence.channel?.getBindings("presence_state")).to(haveCount(1))
expect(presence.channel?.getBindings("presence_diff")).to(haveCount(1))
})
it("binds to channel with custom options ", closure: {
let channel = Channel(topic: "topic", socket: mockSocket)
let customOptions
= Presence.Options(events: [.state: "custom_state",
.diff: "custom_diff"])
let p = Presence(channel: channel, opts: customOptions)
expect(p.channel?.getBindings("presence_state")).to(beEmpty())
expect(p.channel?.getBindings("presence_diff")).to(beEmpty())
expect(p.channel?.getBindings("custom_state")).to(haveCount(1))
expect(p.channel?.getBindings("custom_diff")).to(haveCount(1))
})
it("syncs state and diffs", closure: {
let user1: Presence.Map = ["metas": [["id": 1, "phx_ref": "1"]]]
let user2: Presence.Map = ["metas": [["id": 2, "phx_ref": "2"]]]
let newState: Presence.State = ["u1": user1, "u2": user2]
channel.trigger(event: "presence_state",
payload: newState,
ref: "1")
let s = presence.list(by: listByFirst)
expect(s).to(haveCount(2))
// can't check values because maps are lazy
// expect(s[0]["id"] as? Int).to(equal(1))
// expect(s[0]["phx_ref"] as? String).to(equal("1"))
//
// expect(s[1]["id"] as? Int).to(equal(2))
// expect(s[1]["phx_ref"] as? String).to(equal("2"))
channel.trigger(event: "presence_diff",
payload: ["joins": [:], "leaves": ["u1": user1]],
ref: "2")
let l = presence.list(by: listByFirst)
expect(l).to(haveCount(1))
expect(l[0]["id"] as? Int).to(equal(2))
expect(l[0]["phx_ref"] as? String).to(equal("2"))
})
it("applies pending diff if state is not yet synced", closure: {
var onJoins: [(id: String, current: Presence.Map?, new: Presence.Map)] = []
var onLeaves: [(id: String, current: Presence.Map, left: Presence.Map)] = []
presence.onJoin({ (key, current, new) in
onJoins.append((key, current, new))
})
presence.onLeave({ (key, current, left) in
onLeaves.append((key, current, left))
})
let user1 = ["metas": [["id": 1, "phx_ref": "1"]]]
let user2 = ["metas": [["id": 2, "phx_ref": "2"]]]
let user3 = ["metas": [["id": 3, "phx_ref": "3"]]]
let newState = ["u1": user1, "u2": user2]
let leaves = ["u2": user2]
let payload1 = ["joins": [:], "leaves": leaves]
channel.trigger(event: "presence_diff", payload: payload1, ref: "")
// there is no state
expect(presence.list(by: listByFirst)).to(beEmpty())
// pending diffs 1
expect(presence.pendingDiffs).to(haveCount(1))
expect(presence.pendingDiffs[0]["joins"]).to(beEmpty())
let t1 = transform(presence.pendingDiffs[0]["leaves"]!, and: leaves)
expect(t1.lhs).to(equal(t1.rhs))
channel.trigger(event: "presence_state", payload: newState, ref: "")
expect(onLeaves).to(haveCount(1))
expect(onLeaves[0].id).to(equal("u2"))
expect(onLeaves[0].current["metas"]).to(beEmpty())
expect(onLeaves[0].left["metas"]?[0]["id"] as? Int).to(equal(2))
let s = presence.list(by: listByFirst)
expect(s).to(haveCount(1))
expect(s[0]["id"] as? Int).to(equal(1))
expect(s[0]["phx_ref"] as? String).to(equal("1"))
expect(presence.pendingDiffs).to(beEmpty())
expect(onJoins).to(haveCount(2))
// can't check values because maps are lazy
// expect(onJoins[0].id).to(equal("u1"))
// expect(onJoins[0].current).to(beNil())
// expect(onJoins[0].new["metas"]?[0]["id"] as? Int).to(equal(1))
//
// expect(onJoins[1].id).to(equal("u2"))
// expect(onJoins[1].current).to(beNil())
// expect(onJoins[1].new["metas"]?[0]["id"] as? Int).to(equal(2))
// disconnect then reconnect
expect(presence.isPendingSyncState).to(beFalse())
channel.joinPush.ref = "2"
expect(presence.isPendingSyncState).to(beTrue())
channel.trigger(event: "presence_diff",
payload: ["joins": [:], "leaves": ["u1": user1]],
ref: "")
let d = presence.list(by: listByFirst)
expect(d).to(haveCount(1))
expect(d[0]["id"] as? Int).to(equal(1))
expect(d[0]["phx_ref"] as? String).to(equal("1"))
channel.trigger(event: "presence_state",
payload: ["u1": user1, "u3": user3],
ref: "")
let s2 = presence.list(by: listByFirst)
expect(s2).to(haveCount(1))
expect(s2[0]["id"] as? Int).to(equal(3))
expect(s2[0]["phx_ref"] as? String).to(equal("3"))
})
it("allows custom states", closure: {
let channel = Channel(topic: "topic", socket: mockSocket)
channel.joinPush.ref = "1"
let customOptions
= Presence.Options(events: [.state: "the_state",
.diff: "the_diff"])
let presence = Presence(channel: channel, opts: customOptions)
let user1: Presence.Map = ["metas": [["id": 1, "phx_ref": "1"]]]
channel.trigger(event: "the_state", payload: ["user1": user1], ref: "")
let s = presence.list(by: listByFirst)
expect(s).to(haveCount(1))
expect(s[0]["id"] as? Int).to(equal(1))
expect(s[0]["phx_ref"] as? String).to(equal("1"))
channel.trigger(event: "the_diff",
payload: ["joins": [:], "leaves": ["user1": user1]],
ref: "2")
expect(presence.list(by: listByFirst)).to(beEmpty())
})
}
describe("syncState") {
it("syncs empty state", closure: {
let newState: Presence.State = ["u1": ["metas": [["id":1, "phx_ref": "1"]]]]
var state: Presence.State = [:]
let stateBefore = state
Presence.syncState(state, newState: newState)
let t1 = transform(state, and: stateBefore)
expect(t1.lhs).to(equal(t1.rhs))
state = Presence.syncState(state, newState: newState)
let t2 = transform(state, and: newState)
expect(t2.lhs).to(equal(t2.rhs))
})
it("onJoins new presences and onLeave's left presences", closure: {
let newState = fixState
var state = ["u4": ["metas": [["id":4, "phx_ref": "4"]]]]
var joined: Presence.Diff = [:]
var left: Presence.Diff = [:]
let onJoin: Presence.OnJoin = { key, current, newPres in
var state: Presence.State = ["newPres": newPres]
if let c = current {
state["current"] = c
}
// Diff = [String: Presence.State]
joined[key] = state
}
let onLeave: Presence.OnLeave = { key, current, leftPres in
// Diff = [String: Presence.State]
left[key] = ["current": current, "leftPres": leftPres]
}
let stateBefore = state
Presence.syncState(state, newState: newState,
onJoin: onJoin, onLeave: onLeave)
let t1 = transform(state, and: stateBefore)
expect(t1.lhs).to(equal(t1.rhs))
state = Presence.syncState(state, newState: newState,
onJoin: onJoin, onLeave: onLeave)
let t2 = transform(state, and: newState)
expect(t2.lhs).to(equal(t2.rhs))
// assert equality in joined
let joinedExpectation: Presence.Diff = [
"u1": ["newPres": ["metas": [["id":1, "phx_ref": "1"]] ]],
"u2": ["newPres": ["metas": [["id":2, "phx_ref": "2"]] ]],
"u3": ["newPres": ["metas": [["id":3, "phx_ref": "3"]] ]]
]
let tJoin = transform(joined, and: joinedExpectation)
expect(tJoin.lhs).to(equal(tJoin.rhs))
// assert equality in left
let leftExpectation: Presence.Diff = ["u4": [
"current": ["metas": [] ],
"leftPres": ["metas": [["id":4, "phx_ref": "4"]] ]
] ]
let tLeft = transform(left, and: leftExpectation)
expect(tLeft.lhs).to(equal(tLeft.rhs))
})
it("onJoins only newly added metas", closure: {
var state = ["u3": ["metas": [["id":3, "phx_ref": "3"]]]]
let newState = ["u3": [
"metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.new"]]
]]
var joined: Presence.Diff = [:]
var left: Presence.Diff = [:]
let onJoin: Presence.OnJoin = { key, current, newPres in
var state: Presence.State = ["newPres": newPres]
if let c = current {
state["current"] = c
}
// Diff = [String: Presence.State]
joined[key] = state
}
let onLeave: Presence.OnLeave = { key, current, leftPres in
// Diff = [String: Presence.State]
left[key] = ["current": current, "leftPres": leftPres]
}
state = Presence.syncState(state, newState: newState,
onJoin: onJoin, onLeave: onLeave)
let t2 = transform(state, and: newState)
expect(t2.lhs).to(equal(t2.rhs))
// assert equality in joined
let joinedExpectation: Presence.Diff = [
"u3": ["current": ["metas": [["id":3, "phx_ref": "3"]] ],
"newPres": ["metas": [["id":3, "phx_ref": "3.new"]] ]
]
]
let tJoin = transform(joined, and: joinedExpectation)
expect(tJoin.lhs).to(equal(tJoin.rhs))
// assert equality in left
expect(left).to(beEmpty())
})
it("onLeaves only newly removed metas", closure: {
let newState = ["u3": ["metas": [["id":3, "phx_ref": "3"]]]]
var state = ["u3": [
"metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.left"]]
]]
var joined: Presence.Diff = [:]
var left: Presence.Diff = [:]
let onJoin: Presence.OnJoin = { key, current, newPres in
var state: Presence.State = ["newPres": newPres]
if let c = current {
state["current"] = c
}
// Diff = [String: Presence.State]
joined[key] = state
}
let onLeave: Presence.OnLeave = { key, current, leftPres in
// Diff = [String: Presence.State]
left[key] = ["current": current, "leftPres": leftPres]
}
state = Presence.syncState(state, newState: newState,
onJoin: onJoin, onLeave: onLeave)
let t2 = transform(state, and: newState)
expect(t2.lhs).to(equal(t2.rhs))
// assert equality in joined
let leftExpectation: Presence.Diff = [
"u3": ["current": ["metas": [["id":3, "phx_ref": "3"]] ],
"leftPres": ["metas": [["id":3, "phx_ref": "3.left"]] ]
]
]
let tLeft = transform(left, and: leftExpectation)
expect(tLeft.lhs).to(equal(tLeft.rhs))
// assert equality in left
expect(joined).to(beEmpty())
})
it("syncs both joined and left metas", closure: {
let newState = ["u3": [
"metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.new"]]
]]
var state = ["u3": [
"metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.left"]]
]]
var joined: Presence.Diff = [:]
var left: Presence.Diff = [:]
let onJoin: Presence.OnJoin = { key, current, newPres in
var state: Presence.State = ["newPres": newPres]
if let c = current {
state["current"] = c
}
// Diff = [String: Presence.State]
joined[key] = state
}
let onLeave: Presence.OnLeave = { key, current, leftPres in
// Diff = [String: Presence.State]
left[key] = ["current": current, "leftPres": leftPres]
}
state = Presence.syncState(state, newState: newState,
onJoin: onJoin, onLeave: onLeave)
let t2 = transform(state, and: newState)
expect(t2.lhs).to(equal(t2.rhs))
// assert equality in joined
let joinedExpectation: Presence.Diff = [
"u3": ["current": ["metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.left"]] ],
"newPres": ["metas": [["id":3, "phx_ref": "3.new"]] ]
]
]
let tJoin = transform(joined, and: joinedExpectation)
expect(tJoin.lhs).to(equal(tJoin.rhs))
// assert equality in left
let leftExpectation: Presence.Diff = [
"u3": ["current": ["metas": [["id":3, "phx_ref": "3"], ["id":3, "phx_ref": "3.new"]] ],
"leftPres": ["metas": [["id":3, "phx_ref": "3.left"]] ]
]
]
let tLeft = transform(left, and: leftExpectation)
expect(tLeft.lhs).to(equal(tLeft.rhs))
})
}
describe("syncDiff") {
it("syncs empty state", closure: {
let joins: Presence.State = ["u1": ["metas": [["id":1, "phx_ref": "1"]]]]
var state: Presence.State = [:]
Presence.syncDiff(state, diff: ["joins": joins, "leaves": [:]])
expect(state).to(beEmpty())
state = Presence.syncDiff(state, diff: ["joins": joins, "leaves": [:]])
let t1 = transform(state, and: joins)
expect(t1.lhs).to(equal(t1.rhs))
})
it("removes presence when meta is empty and adds additional meta", closure: {
var state = fixState
let diff: Presence.Diff = ["joins": fixJoins, "leaves": fixLeaves]
state = Presence.syncDiff(state, diff: diff)
let expectation: Presence.State = [
"u1": ["metas": [
["id":1, "phx_ref": "1"],
["id":1, "phx_ref": "1.2"]]
],
"u3": ["metas": [["id":3, "phx_ref": "3"]] ]
]
let t1 = transform(state, and: expectation)
expect(t1.lhs).to(equal(t1.rhs))
})
it("removes meta while leaving key if other metas exist", closure: {
var state: Presence.State = [
"u1": ["metas": [
["id":1, "phx_ref": "1"],
["id":1, "phx_ref": "1.2"]]
]]
let leaves: Presence.State = ["u1": ["metas": [["id":1, "phx_ref": "1"]]]]
let diff: Presence.Diff = ["joins": [:], "leaves": leaves]
state = Presence.syncDiff(state, diff: diff)
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/SocketSpec.swift
================================================
//
// SocketSpec.swift
// SwiftPhoenixClient
//
// Created by Daniel Rees on 2/10/18.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
@available(iOS 13, *)
class SocketSpec: QuickSpec {
let encode = Defaults.encode
let decode = Defaults.decode
override func spec() {
describe("constructor") {
it("sets defaults", closure: {
let socket = Socket("wss://localhost:4000/socket")
expect(socket.channels).to(haveCount(0))
expect(socket.sendBuffer).to(haveCount(0))
expect(socket.ref).to(equal(0))
expect(socket.endPoint).to(equal("wss://localhost:4000/socket"))
expect(socket.stateChangeCallbacks.open).to(beEmpty())
expect(socket.stateChangeCallbacks.close).to(beEmpty())
expect(socket.stateChangeCallbacks.error).to(beEmpty())
expect(socket.stateChangeCallbacks.message).to(beEmpty())
expect(socket.timeout).to(equal(Defaults.timeoutInterval))
expect(socket.heartbeatInterval).to(equal(Defaults.heartbeatInterval))
expect(socket.logger).to(beNil())
expect(socket.reconnectAfter(1)).to(equal(0.010)) // 10ms
expect(socket.reconnectAfter(2)).to(equal(0.050)) // 50ms
expect(socket.reconnectAfter(3)).to(equal(0.100)) // 100ms
expect(socket.reconnectAfter(4)).to(equal(0.150)) // 150ms
expect(socket.reconnectAfter(5)).to(equal(0.200)) // 200ms
expect(socket.reconnectAfter(6)).to(equal(0.250)) // 250ms
expect(socket.reconnectAfter(7)).to(equal(0.500)) // 500ms
expect(socket.reconnectAfter(8)).to(equal(1.000)) // 1_000ms (1s)
expect(socket.reconnectAfter(9)).to(equal(2.000)) // 2_000ms (2s)
expect(socket.reconnectAfter(10)).to(equal(5.00)) // 5_000ms (5s)
expect(socket.reconnectAfter(11)).to(equal(5.00)) // 5_000ms (5s)
})
it("overrides some defaults", closure: {
let socket = Socket("wss://localhost:4000/socket", paramsClosure: { ["one": 2] })
socket.timeout = 40000
socket.heartbeatInterval = 60000
socket.logger = { _ in }
socket.reconnectAfter = { _ in return 10 }
expect(socket.timeout).to(equal(40000))
expect(socket.heartbeatInterval).to(equal(60000))
expect(socket.logger).toNot(beNil())
expect(socket.reconnectAfter(1)).to(equal(10))
expect(socket.reconnectAfter(2)).to(equal(10))
})
// it("sets queue for underlying transport", closure: {
// let socket = Socket(endPoint: "wss://localhost:4000/socket", transport: { (url) -> WebSocketClient in
// let webSocket = WebSocket(url: url)
// webSocket.callbackQueue = DispatchQueue(label: "test_queue")
// return webSocket
// })
// socket.timeout = 40000
// socket.heartbeatInterval = 60000
// socket.logger = { _ in }
// socket.reconnectAfter = { _ in return 10 }
// socket.connect()
// expect(socket.connection).to(beAKindOf(Transport.self))
// expect((socket.connection as! WebSocket).callbackQueue.label).to(equal("test_queue"))
// })
it("should construct a valid URL", closure: {
// test vsn
expect(Socket("http://localhost:4000/socket/websocket",
paramsClosure: { ["token": "abc123"] },
vsn: "1.0.0")
.endPointUrl
.absoluteString)
.to(equal("http://localhost:4000/socket/websocket?vsn=1.0.0&token=abc123"))
// test params
expect(Socket("http://localhost:4000/socket/websocket",
paramsClosure: { ["token": "abc123"] })
.endPointUrl
.absoluteString)
.to(equal("http://localhost:4000/socket/websocket?vsn=2.0.0&token=abc123"))
expect(Socket("ws://localhost:4000/socket/websocket",
paramsClosure: { ["token": "abc123", "user_id": 1] })
.endPointUrl
.absoluteString)
.to(satisfyAnyOf(
// absoluteString does not seem to return a string with the params in a deterministic order
equal("ws://localhost:4000/socket/websocket?vsn=2.0.0&token=abc123&user_id=1"),
equal("ws://localhost:4000/socket/websocket?vsn=2.0.0&user_id=1&token=abc123")
)
)
// test params with spaces
expect(Socket("ws://localhost:4000/socket/websocket",
paramsClosure: { ["token": "abc 123", "user_id": 1] })
.endPointUrl
.absoluteString)
.to(satisfyAnyOf(
// absoluteString does not seem to return a string with the params in a deterministic order
equal("ws://localhost:4000/socket/websocket?vsn=2.0.0&token=abc%20123&user_id=1"),
equal("ws://localhost:4000/socket/websocket?vsn=2.0.0&user_id=1&token=abc%20123")
)
)
})
it("should not introduce any retain cycles", closure: {
// Must remain as a weak var in order to deallocate the socket. This tests that the
// reconnect timer does not old on to the Socket causing a memory leak.
weak var socket = Socket("http://localhost:4000/socket/websocket")
expect(socket).to(beNil())
})
}
describe("params") {
it("changes dynamically with a closure") {
var authToken = "abc123"
let socket = Socket("ws://localhost:4000/socket/websocket", paramsClosure: { ["token": authToken] })
expect(socket.params?["token"] as? String).to(equal("abc123"))
authToken = "xyz987"
expect(socket.params?["token"] as? String).to(equal("xyz987"))
}
}
describe("websocketProtocol") {
it("returns wss when protocol is https", closure: {
let socket = Socket("https://example.com/")
expect(socket.websocketProtocol).to(equal("wss"))
})
it("returns wss when protocol is wss", closure: {
let socket = Socket("wss://example.com/")
expect(socket.websocketProtocol).to(equal("wss"))
})
it("returns ws when protocol is http", closure: {
let socket = Socket("http://example.com/")
expect(socket.websocketProtocol).to(equal("ws"))
})
it("returns ws when protocol is ws", closure: {
let socket = Socket("ws://example.com/")
expect(socket.websocketProtocol).to(equal("ws"))
})
it("returns empty if there is no scheme", closure: {
let socket = Socket("example.com/")
expect(socket.websocketProtocol).to(beEmpty())
})
}
// describe("endPointUrl") {
// it("does nothing with the url", closure: {
// let socket = Socket("http://example.com/websocket")
// expect(socket.endPointUrl.absoluteString).to(equal("http://example.com/websocket"))
// })
//
// it("appends /websocket correctly", closure: {
// let socketA = Socket("wss://example.org/chat/")
// expect(socketA.endPointUrl.absoluteString).to(equal("wss://example.org/chat/websocket"))
//
// let socketB = Socket("ws://example.org/chat")
// expect(socketB.endPointUrl.absoluteString).to(equal("ws://example.org/chat/websocket"))
// })
// }
describe("connect with Websocket") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
socket.skipHeartbeat = true
}
it("establishes websocket connection with endpoint", closure: {
socket.connect()
expect(socket.connection).toNot(beNil())
})
it("set callbacks for connection", closure: {
var open = 0
socket.onOpen { open += 1 }
var close = 0
socket.onClose { close += 1 }
var lastError: (Error, URLResponse?)?
socket.onError(callback: { (error) in lastError = error })
var lastMessage: Message?
socket.onMessage(callback: { (message) in lastMessage = message })
mockWebSocket.readyState = .closed
socket.connect()
mockWebSocket.delegate?.onOpen()
expect(open).to(equal(1))
mockWebSocket.delegate?.onClose(code: 1000, reason: nil)
expect(close).to(equal(1))
mockWebSocket.delegate?.onError(error: TestError.stub, response: nil)
expect(lastError).toNot(beNil())
let data: [Any] = ["2", "6", "topic", "event", ["response": ["go": true], "status": "ok"]]
let text = toWebSocketText(data: data)
mockWebSocket.delegate?.onMessage(message: text)
expect(lastMessage?.payload["go"] as? Bool).to(beTrue())
})
it("removes callbacks", closure: {
var open = 0
socket.onOpen { open += 1 }
var close = 0
socket.onClose { close += 1 }
var lastError: (Error, URLResponse?)?
socket.onError(callback: { (error) in lastError = error })
var lastMessage: Message?
socket.onMessage(callback: { (message) in lastMessage = message })
mockWebSocket.readyState = .closed
socket.releaseCallbacks()
socket.connect()
mockWebSocket.delegate?.onOpen()
expect(open).to(equal(0))
mockWebSocket.delegate?.onClose(code: 1000, reason: nil)
expect(close).to(equal(0))
mockWebSocket.delegate?.onError(error: TestError.stub, response: nil)
expect(lastError).to(beNil())
let data: [Any] = ["2", "6", "topic", "event", ["response": ["go": true], "status": "ok"]]
let text = toWebSocketText(data: data)
mockWebSocket.delegate?.onMessage(message: text)
expect(lastMessage).to(beNil())
})
it("does not connect if already connected", closure: {
mockWebSocket.readyState = .open
socket.connect()
socket.connect()
expect(mockWebSocket.connectCallsCount).to(equal(1))
})
}
describe("disconnect") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
}
it("removes existing connection", closure: {
socket.connect()
socket.disconnect()
expect(socket.connection).to(beNil())
expect(mockWebSocket.disconnectCodeReasonReceivedArguments?.code)
.to(equal(Socket.CloseCode.normal.rawValue))
})
it("flags the socket as closed cleanly", closure: {
expect(socket.closeStatus).to(equal(.unknown))
socket.disconnect()
expect(socket.closeStatus).to(equal(.clean))
})
it("calls callback", closure: {
var callCount = 0
socket.connect()
socket.disconnect(code: .goingAway) {
callCount += 1
}
expect(mockWebSocket.disconnectCodeReasonCalled).to(beTrue())
expect(mockWebSocket.disconnectCodeReasonReceivedArguments?.reason).to(beNil())
expect(mockWebSocket.disconnectCodeReasonReceivedArguments?.code)
.to(equal(Socket.CloseCode.goingAway.rawValue))
expect(callCount).to(equal(1))
})
it("calls onClose for all state callbacks", closure: {
var callCount = 0
socket.onClose {
callCount += 1
}
socket.disconnect()
expect(callCount).to(equal(1))
})
it("invalidates and invalidates the heartbeat timer", closure: {
var timerCalled = 0
let queue = DispatchQueue(label: "test.heartbeat")
let timer = HeartbeatTimer(timeInterval: 10, queue: queue)
timer.start { timerCalled += 1 }
socket.heartbeatTimer = timer
socket.disconnect()
expect(socket.heartbeatTimer?.isValid).to(beFalse())
timer.fire()
expect(timerCalled).to(equal(0))
})
it("does nothing if not connected", closure: {
socket.disconnect()
expect(mockWebSocket.disconnectCodeReasonCalled).to(beFalse())
})
}
describe("channel") {
var socket: Socket!
beforeEach {
socket = Socket("/socket")
}
it("returns channel with given topic and params", closure: {
let channel = socket.channel("topic", params: ["one": "two"])
socket.ref = 1006
// No deep equal, so hack it
expect(channel.socket?.ref).to(equal(socket.ref))
expect(channel.topic).to(equal("topic"))
expect(channel.params["one"] as? String).to(equal("two"))
})
it("adds channel to sockets channel list", closure: {
expect(socket.channels).to(beEmpty())
let channel = socket.channel("topic", params: ["one": "two"])
expect(socket.channels).to(haveCount(1))
expect(socket.channels[0].topic).to(equal(channel.topic))
})
}
describe("remove") {
var socket: Socket!
beforeEach {
socket = Socket("/socket")
}
it("removes given channel from channels", closure: {
let channel1 = socket.channel("topic-1")
let channel2 = socket.channel("topic-2")
channel1.joinPush.ref = "1"
channel2.joinPush.ref = "2"
socket.remove(channel1)
expect(socket.channels).to(haveCount(1))
expect(socket.channels[0].topic).to(equal(channel2.topic))
})
}
describe("push") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
}
it("sends data to connection when connected", closure: {
mockWebSocket.readyState = .open
socket.connect()
socket.push(topic: "topic", event: "event", payload: ["one": "two"], ref: "6", joinRef: "2")
expect(mockWebSocket.sendDataCalled).to(beTrue())
let json = self.decode(mockWebSocket.sendDataReceivedData!) as! [Any]
expect(json[0] as? String).to(equal("2"))
expect(json[1] as? String).to(equal("6"))
expect(json[2] as? String).to(equal("topic"))
expect(json[3] as? String).to(equal("event"))
expect(json[4] as? [String: String]).to(equal(["one": "two"]))
expect(String(data: mockWebSocket.sendDataReceivedData!, encoding: .utf8))
.to(equal("[\"2\",\"6\",\"topic\",\"event\",{\"one\":\"two\"}]"))
})
it("excludes ref information if not passed", closure: {
mockWebSocket.readyState = .open
socket.connect()
socket.push(topic: "topic", event: "event", payload: ["one": "two"])
let json = self.decode(mockWebSocket.sendDataReceivedData!) as! [Any?]
expect(json[0] as? String).to(beNil())
expect(json[1] as? String).to(beNil())
expect(json[2] as? String).to(equal("topic"))
expect(json[3] as? String).to(equal("event"))
expect(json[4] as? [String: String]).to(equal(["one": "two"]))
expect(String(data: mockWebSocket.sendDataReceivedData!, encoding: .utf8))
.to(equal("[null,null,\"topic\",\"event\",{\"one\":\"two\"}]"))
})
it("buffers data when not connected", closure: {
mockWebSocket.readyState = .closed
socket.connect()
expect(socket.sendBuffer).to(beEmpty())
socket.push(topic: "topic1", event: "event1", payload: ["one": "two"])
expect(mockWebSocket.sendDataCalled).to(beFalse())
expect(socket.sendBuffer).to(haveCount(1))
socket.push(topic: "topic2", event: "event2", payload: ["one": "two"])
expect(mockWebSocket.sendDataCalled).to(beFalse())
expect(socket.sendBuffer).to(haveCount(2))
socket.sendBuffer.forEach( { try? $0.callback() } )
expect(mockWebSocket.sendDataCalled).to(beTrue())
expect(mockWebSocket.sendDataCallsCount).to(equal(2))
})
}
describe("makeRef") {
var socket: Socket!
beforeEach {
socket = Socket("/socket")
}
it("returns next message ref", closure: {
expect(socket.ref).to(equal(0))
expect(socket.makeRef()).to(equal("1"))
expect(socket.ref).to(equal(1))
expect(socket.makeRef()).to(equal("2"))
expect(socket.ref).to(equal(2))
})
it("resets to 0 if it hits max int", closure: {
socket.ref = UInt64.max
expect(socket.makeRef()).to(equal("0"))
expect(socket.ref).to(equal(0))
})
}
describe("sendHeartbeat v1") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket("/socket")
socket.connection = mockWebSocket
}
it("closes socket when heartbeat is not ack'd within heartbeat window", closure: {
mockWebSocket.readyState = .open
socket.sendHeartbeat()
expect(mockWebSocket.disconnectCodeReasonCalled).to(beFalse())
expect(socket.pendingHeartbeatRef).toNot(beNil())
socket.sendHeartbeat()
expect(mockWebSocket.disconnectCodeReasonCalled).to(beTrue())
expect(socket.pendingHeartbeatRef).to(beNil())
})
it("pushes heartbeat data when connected", closure: {
mockWebSocket.readyState = .open
socket.sendHeartbeat()
expect(socket.pendingHeartbeatRef).to(equal(String(socket.ref)))
expect(mockWebSocket.sendDataCalled).to(beTrue())
let json = self.decode(mockWebSocket.sendDataReceivedData!) as? [Any?]
expect(json?[0] as? String).to(beNil())
expect(json?[1] as? String).to(equal(socket.pendingHeartbeatRef))
expect(json?[2] as? String).to(equal("phoenix"))
expect(json?[3] as? String).to(equal("heartbeat"))
expect(json?[4] as? [String: String]).to(beEmpty())
expect(String(data: mockWebSocket.sendDataReceivedData!, encoding: .utf8))
.to(equal("[null,\"1\",\"phoenix\",\"heartbeat\",{}]"))
})
it("does nothing when not connected", closure: {
mockWebSocket.readyState = .closed
socket.sendHeartbeat()
expect(mockWebSocket.disconnectCodeReasonCalled).to(beFalse())
expect(mockWebSocket.sendDataCalled).to(beFalse())
})
}
describe("flushSendBuffer") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket("/socket")
socket.connection = mockWebSocket
}
it("calls callbacks in buffer when connected", closure: {
var oneCalled = 0
socket.sendBuffer.append(("0", { oneCalled += 1 }))
var twoCalled = 0
socket.sendBuffer.append(("1", { twoCalled += 1 }))
let threeCalled = 0
mockWebSocket.readyState = .open
socket.flushSendBuffer()
expect(oneCalled).to(equal(1))
expect(twoCalled).to(equal(1))
expect(threeCalled).to(equal(0))
})
it("empties send buffer", closure: {
socket.sendBuffer.append((nil, { }))
mockWebSocket.readyState = .open
socket.flushSendBuffer()
expect(socket.sendBuffer).to(beEmpty())
})
}
describe("removeFromSendBuffer") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket("/socket")
socket.connection = mockWebSocket
}
it("removes a callback with a matching ref", closure: {
var oneCalled = 0
socket.sendBuffer.append(("0", { oneCalled += 1 }))
var twoCalled = 0
socket.sendBuffer.append(("1", { twoCalled += 1 }))
let threeCalled = 0
mockWebSocket.readyState = .open
socket.removeFromSendBuffer(ref: "0")
socket.flushSendBuffer()
expect(oneCalled).to(equal(0))
expect(twoCalled).to(equal(1))
expect(threeCalled).to(equal(0))
})
}
describe("onConnectionOpen") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
var mockTimeoutTimer: TimeoutTimerMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
mockTimeoutTimer = TimeoutTimerMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
socket.reconnectAfter = { _ in return 10 }
socket.reconnectTimer = mockTimeoutTimer
socket.skipHeartbeat = true
mockWebSocket.readyState = .open
socket.connect()
}
it("flushes the send buffer", closure: {
var oneCalled = 0
socket.sendBuffer.append(("0", { oneCalled += 1 }))
socket.onConnectionOpen()
expect(oneCalled).to(equal(1))
expect(socket.sendBuffer).to(beEmpty())
})
it("resets reconnectTimer", closure: {
socket.onConnectionOpen()
expect(mockTimeoutTimer.resetCalled).to(beTrue())
})
it("triggers onOpen callbacks", closure: {
var oneCalled = 0
socket.onOpen { oneCalled += 1 }
var twoCalled = 0
socket.onOpen { twoCalled += 1 }
var threeCalled = 0
socket.onClose { threeCalled += 1 }
socket.onConnectionOpen()
expect(oneCalled).to(equal(1))
expect(twoCalled).to(equal(1))
expect(threeCalled).to(equal(0))
})
}
describe("resetHeartbeat") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
}
it("clears any pending heartbeat", closure: {
socket.pendingHeartbeatRef = "1"
socket.resetHeartbeat()
expect(socket.pendingHeartbeatRef).to(beNil())
})
it("does not schedule heartbeat if skipHeartbeat == true", closure: {
socket.skipHeartbeat = true
socket.resetHeartbeat()
expect(socket.heartbeatTimer).to(beNil())
})
it("creates a timer and sends a heartbeat", closure: {
mockWebSocket.readyState = .open
socket.connect()
socket.heartbeatInterval = 1
expect(socket.heartbeatTimer).to(beNil())
socket.resetHeartbeat()
expect(socket.heartbeatTimer).toNot(beNil())
expect(socket.heartbeatTimer?.timeInterval).to(equal(1))
// Fire the timer
socket.heartbeatTimer?.fire()
expect(mockWebSocket.sendDataCalled).to(beTrue())
let json = self.decode(mockWebSocket.sendDataReceivedData!) as? [Any?]
expect(json?[0] as? String).to(beNil())
expect(json?[1] as? String).to(equal(String(socket.ref)))
expect(json?[2] as? String).to(equal("phoenix"))
expect(json?[3] as? String).to(equal(ChannelEvent.heartbeat))
expect(json?[4] as? [String: Any]).to(beEmpty())
})
it("should invalidate an old timer and create a new one", closure: {
mockWebSocket.readyState = .open
let queue = DispatchQueue(label: "test.heartbeat")
let timer = HeartbeatTimer(timeInterval: 1000, queue: queue)
var timerCalled = 0
timer.start { timerCalled += 1 }
socket.heartbeatTimer = timer
expect(timer.isValid).to(beTrue())
socket.resetHeartbeat()
expect(timer.isValid).to(beFalse())
expect(socket.heartbeatTimer).toNot(equal(timer))
})
}
describe("onConnectionClosed") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
var mockTimeoutTimer: TimeoutTimerMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
mockTimeoutTimer = TimeoutTimerMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
// socket.reconnectAfter = { _ in return 10 }
socket.reconnectTimer = mockTimeoutTimer
// socket.skipHeartbeat = true
}
it("schedules reconnectTimer timeout if normal close", closure: {
socket.onConnectionClosed(code: Socket.CloseCode.normal.rawValue, reason: nil)
expect(mockTimeoutTimer.scheduleTimeoutCalled).to(beTrue())
})
it("does not schedule reconnectTimer timeout if normal close after explicit disconnect", closure: {
socket.disconnect()
expect(mockTimeoutTimer.scheduleTimeoutCalled).to(beFalse())
})
it("schedules reconnectTimer timeout if not normal close", closure: {
socket.onConnectionClosed(code: 1001, reason: nil)
expect(mockTimeoutTimer.scheduleTimeoutCalled).to(beTrue())
})
it("schedules reconnectTimer timeout if connection cannot be made after a previous clean disconnect", closure: {
socket.disconnect()
socket.connect()
socket.onConnectionClosed(code: 1001, reason: nil)
expect(mockTimeoutTimer.scheduleTimeoutCalled).to(beTrue())
})
it("triggers channel error if joining", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join()
expect(channel.state).to(equal(.joining))
socket.onConnectionClosed(code: 1001, reason: nil)
expect(errorCalled).to(beTrue())
})
it("triggers channel error if joined", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join().trigger("ok", payload: [:])
expect(channel.state).to(equal(.joined))
socket.onConnectionClosed(code: 1001, reason: nil)
expect(errorCalled).to(beTrue())
})
it("does not trigger channel error after leave", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join().trigger("ok", payload: [:])
channel.leave()
expect(channel.state).to(equal(.closed))
socket.onConnectionClosed(code: 1001, reason: nil)
expect(errorCalled).to(beFalse())
})
it("triggers onClose callbacks", closure: {
var oneCalled = 0
socket.onClose { oneCalled += 1 }
var twoCalled = 0
socket.onClose { twoCalled += 1 }
var threeCalled = 0
socket.onOpen { threeCalled += 1 }
socket.onConnectionClosed(code: 1000, reason: nil)
expect(oneCalled).to(equal(1))
expect(twoCalled).to(equal(1))
expect(threeCalled).to(equal(0))
})
}
describe("onConnectionError") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
var mockTimeoutTimer: TimeoutTimerMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
mockTimeoutTimer = TimeoutTimerMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
socket.reconnectAfter = { _ in return 10 }
socket.reconnectTimer = mockTimeoutTimer
socket.skipHeartbeat = true
mockWebSocket.readyState = .open
socket.connect()
}
it("triggers onClose callbacks", closure: {
var lastError: (Error, URLResponse?)?
socket.onError(callback: { (error) in lastError = error })
socket.onConnectionError(TestError.stub, response: nil)
expect(lastError).toNot(beNil())
})
it("triggers channel error if joining", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join()
expect(channel.state).to(equal(.joining))
socket.onConnectionError(TestError.stub, response: nil)
expect(errorCalled).to(beTrue())
})
it("triggers channel error if joined", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join().trigger("ok", payload: [:])
expect(channel.state).to(equal(.joined))
socket.onConnectionError(TestError.stub, response: nil)
expect(errorCalled).to(beTrue())
})
it("does not trigger channel error after leave", closure: {
let channel = socket.channel("topic")
var errorCalled = false
channel.on(ChannelEvent.error, callback: { _ in
errorCalled = true
})
channel.join().trigger("ok", payload: [:])
channel.leave()
expect(channel.state).to(equal(.closed))
socket.onConnectionError(TestError.stub, response: nil)
expect(errorCalled).to(beFalse())
})
}
describe("onConnectionMessage") {
// Mocks
var mockWebSocket: PhoenixTransportMock!
var mockTimeoutTimer: TimeoutTimerMock!
let mockWebSocketTransport: ((URL) -> PhoenixTransportMock) = { _ in return mockWebSocket }
// UUT
var socket: Socket!
beforeEach {
mockWebSocket = PhoenixTransportMock()
mockTimeoutTimer = TimeoutTimerMock()
socket = Socket(endPoint: "/socket", transport: mockWebSocketTransport)
socket.reconnectAfter = { _ in return 10 }
socket.reconnectTimer = mockTimeoutTimer
socket.skipHeartbeat = true
mockWebSocket.readyState = .open
socket.connect()
}
it("parses raw message and triggers channel event", closure: {
let targetChannel = socket.channel("topic")
let otherChannel = socket.channel("off-topic")
var targetMessage: Message?
targetChannel.on("event", callback: { (msg) in targetMessage = msg })
var otherMessage: Message?
otherChannel.on("event", callback: { (msg) in otherMessage = msg })
let data: [Any?] = [nil, nil, "topic", "event", ["status": "ok", "response": ["one": "two"]]]
let rawMessage = toWebSocketText(data: data)
socket.onConnectionMessage(rawMessage)
expect(targetMessage?.topic).to(equal("topic"))
expect(targetMessage?.event).to(equal("event"))
expect(targetMessage?.payload["one"] as? String).to(equal("two"))
expect(otherMessage).to(beNil())
})
it("triggers onMessage callbacks", closure: {
var message: Message?
socket.onMessage(callback: { (msg) in message = msg })
let data: [Any?] = [nil, nil, "topic", "event", ["status": "ok", "response": ["one": "two"]]]
let rawMessage = toWebSocketText(data: data)
socket.onConnectionMessage(rawMessage)
expect(message?.topic).to(equal("topic"))
expect(message?.event).to(equal("event"))
expect(message?.payload["one"] as? String).to(equal("two"))
})
it("clears pending heartbeat", closure: {
socket.pendingHeartbeatRef = "5"
let rawMessage = "[null,\"5\",\"phoenix\",\"phx_reply\",{\"response\":{},\"status\":\"ok\"}]"
socket.onConnectionMessage(rawMessage)
expect(socket.pendingHeartbeatRef).to(beNil())
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/TimeoutTimerSpec.swift
================================================
//
// TimeoutTimerSpec.swift
// SwiftPhoenixClientTests
//
// Created by Daniel Rees on 2/10/19.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class TimeoutTimerSpec: QuickSpec {
public func secondsBetweenDates(_ first: Date, _ second: Date) -> Double {
var diff = first.timeIntervalSince1970 - second.timeIntervalSince1970
diff = fabs(diff)
return diff
}
override func spec() {
let fakeClock = FakeTimerQueue()
var timer: TimeoutTimer!
beforeEach {
fakeClock.reset()
timer = TimeoutTimer()
timer.queue = fakeClock
}
describe("retain cycles") {
it("does not hold any retain cycles", closure: {
// var weakTimer: TimeoutTimer? = TimeoutTimer()
//
// var weakTimerCalled: Bool = false
// timer.callback.delegate(to: weakTimer!) { (_) in
// weakTimerCalled = true
// }
//
// timer.timerCalculation.delegate(to: weakTimer!) { _,_ in 1.0 }
//
//
// timer.scheduleTimeout()
// fakeClock.tick(600)
// weakTimer = nil
//
// fakeClock.tick(600)
// expect(timer.tries).to(equal(1))
// expect(weakTimerCalled).to(beFalse())
})
}
describe("scheduleTimeout") {
it("schedules timeouts, resets the timer, and schedules another timeout", closure: {
var callbackTimes: [Date] = []
timer.callback.delegate(to: self) { (_) in
callbackTimes.append(Date())
}
timer.timerCalculation.delegate(to: self) { (_, tries) -> TimeInterval in
return tries > 2 ? 10.0 : [1.0, 2.0, 5.0][tries - 1]
}
timer.scheduleTimeout()
fakeClock.tick(1100)
expect(timer.tries).to(equal(1))
timer.scheduleTimeout()
fakeClock.tick(2100)
expect(timer.tries).to(equal(2))
timer.reset()
timer.scheduleTimeout()
fakeClock.tick(1100)
expect(timer.tries).to(equal(1))
})
it("does not start timer if no interval is provided", closure: {
timer.scheduleTimeout()
expect(timer.workItem).to(beNil())
})
}
}
}
================================================
FILE: Tests/SwiftPhoenixClientTests/URLSessionTransportSpec.swift
================================================
//
// URLSessionTransportSpec.swift
// SwiftPhoenixClientTests
//
// Created by Daniel Rees on 4/1/21.
// Copyright © 2021 SwiftPhoenixClient. All rights reserved.
//
import Quick
import Nimble
@testable import SwiftPhoenixClient
class URLSessionTransportSpec: QuickSpec {
override func spec() {
describe("init") {
it("replaces http with ws protocols") {
if #available(iOS 13, *) {
expect(
URLSessionTransport(url: URL(string:"http://localhost:4000/socket/websocket")!)
.url.absoluteString
).to(equal("ws://localhost:4000/socket/websocket"))
expect(
URLSessionTransport(url: URL(string:"https://localhost:4000/socket/websocket")!)
.url.absoluteString
).to(equal("wss://localhost:4000/socket/websocket"))
expect(
URLSessionTransport(url: URL(string:"ws://localhost:4000/socket/websocket")!)
.url.absoluteString
).to(equal("ws://localhost:4000/socket/websocket"))
expect(
URLSessionTransport(url: URL(string:"wss://localhost:4000/socket/websocket")!)
.url.absoluteString
).to(equal("wss://localhost:4000/socket/websocket"))
} else {
// Fallback on earlier versions
expect("wrong iOS version").to(equal("You must run this test on an iOS 13 device"))
}
}
it("accepts an override for the configuration") {
if #available(iOS 13, *) {
let configuration = URLSessionConfiguration.default
expect(
URLSessionTransport(url: URL(string:"wss://localhost:4000")!, configuration: configuration)
.configuration
).to(equal(configuration))
} else {
// Fallback on earlier versions
expect("wrong iOS version").to(equal("You must run this test on an iOS 13 device"))
}
}
}
}
}
================================================
FILE: docs/Classes/Channel.html
================================================
Channel Class Reference
The topic of the Channel. e.g. “rooms:friends”
Declaration
Swift
public let topic : String
The params sent when joining the channel
Declaration
Swift
public var params : Payload { get set }
Overridable message hook. Receives all events for specialized message
handling before dispatching to the channel callbacks.
return: Must return the message, modified or unmodified
Parameters
msg
The Message received by the client from the server
Declaration
Swift
@discardableResult
public func join ( timeout : TimeInterval ? = nil ) -> Push
Parameters
timeout
Optional. Defaults to Channel’s timeout
Hook into when the Channel is closed. Does not handle retain cycles.
Use delegateOnClose(to:) for automatic handling of retain cycles.
Example:
let channel = socket . channel ( "topic" )
channel . onClose () { [ weak self ] message in
self ? . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func onClose ( _ callback : @escaping (( Message ) -> Void )) -> Int
Parameters
callback
Called when the Channel closes
Hook into when the Channel is closed. Automatically handles retain
cycles. Use onClose() to handle yourself.
Example:
let channel = socket . channel ( "topic" )
channel . delegateOnClose ( to : self ) { ( self , message ) in
self . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOnClose < Target : AnyObject > ( to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Channel closes
Hook into when the Channel receives an Error. Does not handle retain
cycles. Use delegateOnError(to:) for automatic handling of retain
cycles.
Example:
let channel = socket . channel ( "topic" )
channel . onError () { [ weak self ] ( message ) in
self ? . print ( "Channel \( message . topic ) has errored"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func onError ( _ callback : @escaping (( _ message : Message ) -> Void )) -> Int
Parameters
callback
Called when the Channel closes
Hook into when the Channel receives an Error. Automatically handles
retain cycles. Use onError() to handle yourself.
Example:
let channel = socket . channel ( "topic" )
channel . delegateOnError ( to : self ) { ( self , message ) in
self . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOnError < Target : AnyObject > ( to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Channel closes
Subscribes on channel events. Does not handle retain cycles. Use
delegateOn(_:, to:) for automatic handling of retain cycles.
Subscription returns a ref counter, which can be used later to
unsubscribe the exact event listener
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . on ( "event" ) { [ weak self ] ( message ) in
self ? . print ( "do stuff" )
}
let ref2 = channel . on ( "event" ) { [ weak self ] ( message ) in
self ? . print ( "do other stuff" )
}
channel . off ( "event" , ref1 )
Since unsubscription of ref1, “do stuff” won’t print, but “do other
stuff” will keep on printing on the “event”
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func on ( _ event : String , callback : @escaping (( Message ) -> Void )) -> Int
Parameters
event
callback
Called with the event’s message
Subscribes on channel events. Automatically handles retain cycles. Use
on() to handle yourself.
Subscription returns a ref counter, which can be used later to
unsubscribe the exact event listener
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . delegateOn ( "event" , to : self ) { ( self , message ) in
self ? . print ( "do stuff" )
}
let ref2 = channel . delegateOn ( "event" , to : self ) { ( self , message ) in
self ? . print ( "do other stuff" )
}
channel . off ( "event" , ref1 )
Since unsubscription of ref1, “do stuff” won’t print, but “do other
stuff” will keep on printing on the “event”
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOn < Target : AnyObject > ( _ event : String ,
to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
event
owner
Class registering the callback. Usually self
callback
Called with the event’s message
Unsubscribes from a channel event. If a ref is given, only the exact
listener will be removed. Else all listeners for the event will be
removed.
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . on ( "event" ) { _ in print ( "ref1 event" }
let ref2 = channel . on ( "event" ) { _ in print ( "ref2 event" }
let ref3 = channel . on ( "other_event" ) { _ in print ( "ref3 other" }
let ref4 = channel . on ( "other_event" ) { _ in print ( "ref4 other" }
channel . off ( "event" , ref1 )
channel . off ( "other_event" )
After this, only “ref2 event” will be printed if the channel receives
“event” and nothing is printed if the channel receives “other_event”.
paramter ref: Ref counter returned when subscribing. Can be omitted
Declaration
Swift
public func off ( _ event : String , ref : Int ? = nil )
Parameters
event
Event to unsubscribe from
Push a payload to the Channel
Example:
channel
. push ( "event" , payload : [ "message" : "hello" )
. receive ( "ok" ) { _ in { print ( "message sent" ) }
Declaration
Swift
@discardableResult
public func push ( _ event : String ,
payload : Payload ,
timeout : TimeInterval = Defaults . timeoutInterval ) -> Push
Leaves the channel
Unsubscribes from server events, and instructs channel to terminate on
server
Triggers onClose() hooks
To receive leave acknowledgements, use the a receive
hook to bind to the server ack, ie:
Example:
/
channel.leave().receive(“ok”) { _ in { print(“left”) }
return: Push that can add receive hooks
Declaration
Swift
@discardableResult
public func leave ( timeout : TimeInterval = Defaults . timeoutInterval ) -> Push
Overridable message hook. Receives all events for specialized message
handling before dispatching to the channel callbacks.
return: Must return the payload, modified or unmodified
Parameters
event
The event the message was for
payload
The payload for the message
ref
The reference of the message
return: True if the Channel has been closed
Declaration
Swift
public var isClosed : Bool { get }
return: True if the Channel experienced an error
Declaration
Swift
public var isErrored : Bool { get }
return: True if the channel has joined
Declaration
Swift
public var isJoined : Bool { get }
return: True if the channel has requested to join
Declaration
Swift
public var isJoining : Bool { get }
return: True if the channel has requested to leave
Declaration
Swift
public var isLeaving : Bool { get }
================================================
FILE: docs/Classes/Defaults.html
================================================
Defaults Class Reference
Defaults
A collection of default values and behaviors used accross the Client
Default timeout when sending messages
Declaration
Swift
public static let timeoutInterval : TimeInterval
Default interval to send heartbeats on
Declaration
Swift
public static let heartbeatInterval : TimeInterval
Default reconnect algorithm for the socket
Declaration
Swift
public static let reconnectSteppedBackOff : ( Int ) -> TimeInterval
Default rejoin algorithm for individual channels
Declaration
Swift
public static let rejoinSteppedBackOff : ( Int ) -> TimeInterval
Default encode function, utilizing JSONSerialization.data
Declaration
Swift
public static let encode : ([ String : Any ]) -> Data
Default decode function, utilizing JSONSerialization.jsonObject
Declaration
Swift
public static let decode : ( Data ) -> [ String : Any ]?
================================================
FILE: docs/Classes/Message.html
================================================
Message Class Reference
Message
Data that is received from the Server.
Reference number. Empty if missing
Declaration
Swift
public let ref : String
Declaration
Swift
public let topic : String
Declaration
Swift
public let event : String
Convenience accessor. Equivalent to getting the status as such:
message . payload [ "status" ]
Declaration
Swift
public var status : String ? { get }
================================================
FILE: docs/Classes/Presence/Events.html
================================================
Events Enumeration Reference
SwiftPhoenixClient Reference
Events Enumeration Reference
Events
public enum Events : String
Presense Events
Declaration
Swift
case state = "state"
================================================
FILE: docs/Classes/Presence/Options.html
================================================
Options Structure Reference
SwiftPhoenixClient Reference
Options Structure Reference
Options
Custom options that can be provided when creating Presence
Example:
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Default set of Options used when creating Presence. Uses the
phoenix events “presence_state” and “presence_diff”
Declaration
Swift
public static let defaults : Presence . Options
================================================
FILE: docs/Classes/Presence.html
================================================
Presence Class Reference
Presence
public final class Presence
The Presence object provides features for syncing presence information from
the server with the client and handling presences joining and leaving.
Syncing state from the server
To sync presence state from the server, first instantiate an object and pass
your channel in to track lifecycle events:
let channel = socket . channel ( "some:topic" )
let presence = Presence ( channel )
If you have custom syncing state events, you can configure the Presence
object to use those instead.
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Next, use the presence.onSync callback to react to state changes from the
server. For example, to render the list of users every time the list
changes, you could write:
presence . onSync { renderUsers ( presence . list ()) }
Listing Presences
presence.list is used to return a list of presence information based on the
local state of metadata. By default, all presence metadata is returned, but
a listBy function can be supplied to allow the client to select which
metadata to use for a given presence. For example, you may have a user
online from different devices with a metadata status of “online”, but they
have set themselves to “away” on another device. In this case, the app may
choose to use the “away” status for what appears on the UI. The example
below defines a listBy function which prioritizes the first metadata which
was registered for each user. This could be the first tab they opened, or
the first device they came online from:
let listBy : ( String , Presence . Map ) -> Presence . Meta = { id , pres in
let first = pres [ "metas" ] !. first !
first [ "count" ] = pres [ "metas" ] !. count
first [ "id" ] = id
return first
}
let onlineUsers = presence . list ( by : listBy )
(NOTE: The underlying behavior is a map on the presence.state. You are
mapping the state dictionary into whatever datastructure suites your needs)
Handling individual presence join and leave events
The presence.onJoin and presence.onLeave callbacks can be used to react to
individual presences joining and leaving the app. For example:
let presence = Presence ( channel )
presence . onJoin { [ weak self ] ( key , current , newPres ) in
if let cur = current {
print ( "user additional presence" , cur )
} else {
print ( "user entered for the first time" , newPres )
}
}
presence . onLeave { [ weak self ] ( key , current , leftPres ) in
if current [ "metas" ]? . isEmpty == true {
print ( "user has left from all devices" , leftPres )
} else {
print ( "user left from a device" , current )
}
}
presence . onSync { renderUsers ( presence . list ()) }
Custom options that can be provided when creating Presence
Example:
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
See more
Declaration
Swift
public struct Options
Declaration
Swift
public enum Events : String
Meta details of a Presence. Just a dictionary of properties
Declaration
Swift
public typealias Meta = [ String : Any ]
A mapping of a String to an array of Metas. e.g. {“metas”: [{id: 1}]}
Declaration
Swift
public typealias Map = [ String : [ Meta ]]
A mapping of a Presence state to a mapping of Metas
Declaration
Swift
public typealias State = [ String : Map ]
Declaration
Swift
public typealias Diff = [ String : State ]
Closure signature of OnJoin callbacks
Declaration
Swift
public typealias OnJoin = ( _ key : String , _ current : Map ?, _ new : Map ) -> Void
Closure signature for OnLeave callbacks
Declaration
Swift
public typealias OnLeave = ( _ key : String , _ current : Map , _ left : Map ) -> Void
/ Closure signature for OnSync callbacks
Declaration
Swift
public typealias OnSync = () -> Void
The state of the Presence
Declaration
Swift
private(set) public var state : State
Pending join and leave diffs that need to be synced
Declaration
Swift
private(set) public var pendingDiffs : [ Diff ]
The channel’s joinRef, set when state events occur
Declaration
Swift
private(set) public var joinRef : String ?
Declaration
Swift
public var isPendingSyncState : Bool { get }
Callback to be informed of joins
Declaration
Swift
public var onJoin : OnJoin { get set }
Declaration
Swift
public func onJoin ( _ callback : @escaping OnJoin )
Callback to be informed of leaves
Declaration
Swift
public var onLeave : OnLeave { get set }
Declaration
Swift
public func onLeave ( _ callback : @escaping OnLeave )
Callback to be informed of synces
Declaration
Swift
public var onSync : OnSync { get set }
Declaration
Swift
public func onSync ( _ callback : @escaping OnSync )
Returns the array of presences, with deault selected metadata.
Declaration
Swift
public func list () -> [ Map ]
Returns the array of presences, with selected metadata
Declaration
Swift
public func list < T > ( by transformer : ( String , Map ) -> T ) -> [ T ]
Filter the Presence state with a given function
Declaration
Swift
public func filter ( by filter : (( String , Map ) -> Bool )?) -> State
Declaration
Swift
@discardableResult
public static func syncState ( _ currentState : State ,
newState : State ,
onJoin : OnJoin = { _ , _ , _ in },
onLeave : OnLeave = { _ , _ , _ in }) -> State
Declaration
Swift
@discardableResult
public static func syncDiff ( _ currentState : State ,
diff : Diff ,
onJoin : OnJoin = { _ , _ , _ in },
onLeave : OnLeave = { _ , _ , _ in }) -> State
Declaration
Swift
public static func filter ( _ presences : State ,
by filter : (( String , Map ) -> Bool )?) -> State
Declaration
Swift
public static func listBy < T > ( _ presences : State ,
transformer : ( String , Map ) -> T ) -> [ T ]
================================================
FILE: docs/Classes/Push.html
================================================
Push Class Reference
The channel sending the Push
Declaration
Swift
public weak var channel : Channel ?
The event, for example phx_join
Declaration
Swift
public let event : String
The payload, for example [“user_id”: “abc123”]
The push timeout. Default is 10.0 seconds
Declaration
Swift
public var timeout : TimeInterval
Resets and sends the Push
Declaration
Swift
public func resend ( _ timeout : TimeInterval = Defaults . timeoutInterval )
Parameters
timeout
Optional. The push timeout. Default is 10.0s
Sends the Push. If it has already timed out, then the call will
be ignored and return early. Use resend in this case.
Receive a specific event when sending an Outbound message. Subscribing
to status events with this method does not guarantees no retain cycles.
You should pass weak self in the capture list of the callback. You
can call `.delegateReceive(status:, to:, callback:) and the library will
handle it for you.
Example:
channel
. send ( event : "custom" , payload : [ "body" : "example" ])
. receive ( "error" ) { [ weak self ] payload in
print ( "Error: " , payload )
}
Declaration
Swift
@discardableResult
public func receive ( _ status : String ,
callback : @escaping (( Message ) -> ())) -> Push
Parameters
status
callback
Callback to fire when the status is recevied
Receive a specific event when sending an Outbound message. Automatically
prevents retain cycles. See manualReceive(status:, callback:) if you
want to handle this yourself.
Example:
channel
. send ( event : "custom" , payload : [ "body" : "example" ])
. delegateReceive ( "error" , to : self ) { payload in
print ( "Error: " , payload )
}
Declaration
Swift
@discardableResult
public func delegateReceive < Target : AnyObject > ( _ status : String ,
to owner : Target ,
callback : @escaping (( Target , Message ) -> ())) -> Push
Parameters
status
owner
The class that is calling .receive. Usually self
callback
Callback to fire when the status is recevied
================================================
FILE: docs/Classes/Socket.html
================================================
Socket Class Reference
Socket
public class Socket
extension Socket : WebSocketDelegate
Socket Connection
A single connection is established to the server and
channels are multiplexed over the connection.
Connect to the server using the Socket class:
let socket = new Socket ( "/socket" , paramsClosure : { [ "userToken" : "123" ] })
socket . connect ()
The Socket constructor takes the mount point of the socket,
the authentication params, as well as options that can be found in
the Socket docs, such as configuring the heartbeat.
The string WebSocket endpoint (ie "ws://example.com/socket",
"wss://example.com", etc.) That was passed to the Socket during
initialization. The URL endpoint will be modified by the Socket to
include "/websocket" if missing.
Declaration
Swift
public let endPoint : String
The fully qualified socket URL
Declaration
Swift
public private(set) var endPointUrl : URL
Resolves to return the paramsClosure result at the time of calling.
If the Socket was created with static params, then those will be
returned every time.
Declaration
Swift
public var params : Payload ? { get }
The optional params closure used to get params whhen connecting. Must
be set when initializaing the Socket.
Override to provide custom encoding of data before writing to the socket
Declaration
Swift
public var encode : ([ String : Any ]) -> Data
Override to provide customd decoding of data read from the socket
Declaration
Swift
public var decode : ( Data ) -> [ String : Any ]?
Timeout to use when opening connections
Declaration
Swift
public var timeout : TimeInterval
Interval between sending a heartbeat
Declaration
Swift
public var heartbeatInterval : TimeInterval
Interval between socket reconnect attempts, in seconds
Declaration
Swift
public var reconnectAfter : ( Int ) -> TimeInterval
Interval between channel rejoin attempts, in seconds
Declaration
Swift
public var rejoinAfter : ( Int ) -> TimeInterval
The optional function to receive logs
Declaration
Swift
public var logger : (( String ) -> Void )?
Disables heartbeats from being sent. Default is false.
Declaration
Swift
public var skipHeartbeat : Bool
Enable/Disable SSL certificate validation. Default is false. This
must be set before calling socket.connect() in order to be applied
Declaration
Swift
public var disableSSLCertValidation : Bool
Configure custom SSL validation logic, eg. SSL pinning. This
must be set before calling socket.connect() in order to apply.
Declaration
Swift
public var security : SSLTrustValidator ?
Configure the encryption used by your client by setting the
allowed cipher suites supported by your server. This must be
set before calling socket.connect() in order to apply.
Declaration
Swift
public var enabledSSLCipherSuites : [ SSLCipherSuite ]?
Declaration
Swift
public convenience init ( _ endPoint : String ,
params : Payload ? = nil )
Declaration
Swift
public convenience init ( _ endPoint : String ,
paramsClosure : PayloadClosure ?)
return: The socket protocol, wss or ws
Declaration
Swift
public var websocketProtocol : String { get }
return: True if the socket is connected
Declaration
Swift
public var isConnected : Bool { get }
Connects the Socket. The params passed to the Socket on initialization
will be sent through the connection. If the Socket is already connected,
then this call will be ignored.
Declaration
Swift
public func connect ()
Disconnects the socket
paramter callback: Optional. Called when disconnected
Declaration
Swift
public func disconnect ( code : CloseCode = CloseCode . normal ,
callback : (() -> Void )? = nil )
Parameters
code
Optional. Closing status code
Register Socket State Callbacks
Registers callbacks for connection open events. Does not handle retain
cycles. Use delegateOnOpen(to:) for automatic handling of retain cycles.
Example:
socket . onOpen () { [ weak self ] in
self ? . print ( "Socket Connection Open" )
}
Declaration
Swift
public func onOpen ( callback : @escaping () -> Void )
Parameters
callback
Called when the Socket is opened
Registers callbacks for connection open events. Automatically handles
retain cycles. Use onOpen() to handle yourself.
Example:
socket . delegateOnOpen ( to : self ) { self in
self . print ( "Socket Connection Open" )
}
Declaration
Swift
public func delegateOnOpen < T : AnyObject > ( to owner : T ,
callback : @escaping (( T ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket is opened
Registers callbacks for connection close events. Does not handle retain
cycles. Use delegateOnClose(_:) for automatic handling of retain cycles.
Example:
socket . onClose () { [ weak self ] in
self ? . print ( "Socket Connection Close" )
}
Declaration
Swift
public func onClose ( callback : @escaping () -> Void )
Parameters
callback
Called when the Socket is closed
Registers callbacks for connection close events. Automatically handles
retain cycles. Use onClose() to handle yourself.
Example:
socket . delegateOnClose ( self ) { self in
self . print ( "Socket Connection Close" )
}
Declaration
Swift
public func delegateOnClose < T : AnyObject > ( to owner : T ,
callback : @escaping (( T ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket is closed
Registers callbacks for connection error events. Does not handle retain
cycles. Use delegateOnError(to:) for automatic handling of retain cycles.
Example:
socket . onError () { [ weak self ] ( error ) in
self ? . print ( "Socket Connection Error" , error )
}
Declaration
Swift
public func onError ( callback : @escaping ( Error ) -> Void )
Parameters
callback
Called when the Socket errors
Registers callbacks for connection error events. Automatically handles
retain cycles. Use manualOnError() to handle yourself.
Example:
socket . delegateOnError ( to : self ) { ( self , error ) in
self . print ( "Socket Connection Error" , error )
}
Declaration
Swift
public func delegateOnError < T : AnyObject > ( to owner : T ,
callback : @escaping (( T , Error ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket errors
Registers callbacks for connection message events. Does not handle
retain cycles. Use delegateOnMessage(_to:) for automatic handling of
retain cycles.
Example:
socket . onMessage () { [ weak self ] ( message ) in
self ? . print ( "Socket Connection Message" , message )
}
Declaration
Swift
public func onMessage ( callback : @escaping ( Message ) -> Void )
Parameters
callback
Called when the Socket receives a message event
Registers callbacks for connection message events. Automatically handles
retain cycles. Use onMessage() to handle yourself.
Example:
socket . delegateOnMessage ( self ) { ( self , message ) in
self . print ( "Socket Connection Message" , message )
}
Declaration
Swift
public func delegateOnMessage < T : AnyObject > ( to owner : T ,
callback : @escaping (( T , Message ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket receives a message event
Releases all stored callback hooks (onError, onOpen, onClose, etc.) You should
call this method when you are finished when the Socket in order to release
any references held by the socket.
Declaration
Swift
public func releaseCallbacks ()
Initialize a new Channel
Example:
let channel = socket . channel ( "rooms" , params : [ "user_id" : "abc123" ])
Declaration
Swift
public func channel ( _ topic : String ,
params : [ String : Any ] = [:]) -> Channel
Parameters
topic
params
Optional. Parameters for the channel
Removes the Channel from the socket. This does not cause the channel to
inform the server that it is leaving. You should call channel.leave()
prior to removing the Channel.
Example:
channel . leave ()
socket . remove ( channel )
Declaration
Swift
public func remove ( _ channel : Channel )
return: the next message ref, accounting for overflows
Declaration
Swift
public func makeRef () -> String
Declaration
Swift
public func websocketDidConnect ( socket : WebSocketClient )
Declaration
Swift
public func websocketDidDisconnect ( socket : WebSocketClient , error : Error ?)
Declaration
Swift
public func websocketDidReceiveMessage ( socket : WebSocketClient , text : String )
Declaration
Swift
public func websocketDidReceiveData ( socket : WebSocketClient , data : Data )
================================================
FILE: docs/Classes.html
================================================
Classes Reference
Classes
The following classes are available globally.
Declaration
Swift
public class Channel
Data that is received from the Server.
See more
Declaration
Swift
public class Message
The Presence object provides features for syncing presence information from
the server with the client and handling presences joining and leaving.
Syncing state from the server
To sync presence state from the server, first instantiate an object and pass
your channel in to track lifecycle events:
let channel = socket . channel ( "some:topic" )
let presence = Presence ( channel )
If you have custom syncing state events, you can configure the Presence
object to use those instead.
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Next, use the presence.onSync callback to react to state changes from the
server. For example, to render the list of users every time the list
changes, you could write:
presence . onSync { renderUsers ( presence . list ()) }
Listing Presences
presence.list is used to return a list of presence information based on the
local state of metadata. By default, all presence metadata is returned, but
a listBy function can be supplied to allow the client to select which
metadata to use for a given presence. For example, you may have a user
online from different devices with a metadata status of “online”, but they
have set themselves to “away” on another device. In this case, the app may
choose to use the “away” status for what appears on the UI. The example
below defines a listBy function which prioritizes the first metadata which
was registered for each user. This could be the first tab they opened, or
the first device they came online from:
let listBy : ( String , Presence . Map ) -> Presence . Meta = { id , pres in
let first = pres [ "metas" ] !. first !
first [ "count" ] = pres [ "metas" ] !. count
first [ "id" ] = id
return first
}
let onlineUsers = presence . list ( by : listBy )
(NOTE: The underlying behavior is a map on the presence.state. You are
mapping the state dictionary into whatever datastructure suites your needs)
Handling individual presence join and leave events
The presence.onJoin and presence.onLeave callbacks can be used to react to
individual presences joining and leaving the app. For example:
let presence = Presence ( channel )
presence . onJoin { [ weak self ] ( key , current , newPres ) in
if let cur = current {
print ( "user additional presence" , cur )
} else {
print ( "user entered for the first time" , newPres )
}
}
presence . onLeave { [ weak self ] ( key , current , leftPres ) in
if current [ "metas" ]? . isEmpty == true {
print ( "user has left from all devices" , leftPres )
} else {
print ( "user left from a device" , current )
}
}
presence . onSync { renderUsers ( presence . list ()) }
See more
Declaration
Swift
public final class Presence
Socket Connection
A single connection is established to the server and
channels are multiplexed over the connection.
Connect to the server using the Socket class:
let socket = new Socket ( "/socket" , paramsClosure : { [ "userToken" : "123" ] })
socket . connect ()
The Socket constructor takes the mount point of the socket,
the authentication params, as well as options that can be found in
the Socket docs, such as configuring the heartbeat.
See more
Declaration
Swift
public class Socket
extension Socket : WebSocketDelegate
A collection of default values and behaviors used accross the Client
See more
Declaration
Swift
public class Defaults
================================================
FILE: docs/Enums/ChannelState.html
================================================
ChannelState Enumeration Reference
SwiftPhoenixClient Reference
ChannelState Enumeration Reference
ChannelState
public enum ChannelState : String
Represents the multiple states that a Channel can be in
throughout it’s lifecycle.
Declaration
Swift
case closed = "closed"
Declaration
Swift
case errored = "errored"
Declaration
Swift
case joined = "joined"
Declaration
Swift
case joining = "joining"
Declaration
Swift
case leaving = "leaving"
================================================
FILE: docs/Enums.html
================================================
Enumerations Reference
Enumerations
The following enumerations are available globally.
Represents the multiple states that a Channel can be in
throughout it’s lifecycle.
See more
Declaration
Swift
public enum ChannelState : String
================================================
FILE: docs/Global Variables.html
================================================
Global Variables Reference
Global Variables
The following global variables are available globally.
Default timeout when making a connection set to 10 seconds
Declaration
Swift
public let PHOENIX_DEFAULT_TIMEOUT : Int
Declaration
Swift
public let PHOENIX_TIMEOUT_INTERVAL : TimeInterval
Default heartbeat interval set to 30 seconds
Declaration
Swift
public let PHOENIX_DEFAULT_HEARTBEAT : Int
Default heartbeat interval set to 30.0 seconds
Declaration
Swift
public let PHOENIX_HEARTBEAT_INTERVAL : TimeInterval
================================================
FILE: docs/Protocols/Serializer.html
================================================
Serializer Protocol Reference
SwiftPhoenixClient Reference
Serializer Protocol Reference
Serializer
public protocol Serializer
Provides customization when enoding and decoding data within the Socket
Convert a message into Data to be sent over the Socket
Declaration
Swift
func encode ( _ message : [ String : Any ]) throws -> Data
Convert data from the Socket into a Message
Declaration
Swift
func decode ( _ data : Data ) -> Message ?
================================================
FILE: docs/Protocols.html
================================================
Protocols Reference
Protocols
The following protocols are available globally.
Provides customization when enoding and decoding data within the Socket
See more
Declaration
Swift
public protocol Serializer
================================================
FILE: docs/Structs/ChannelEvent.html
================================================
ChannelEvent Structure Reference
SwiftPhoenixClient Reference
ChannelEvent Structure Reference
ChannelEvent
public struct ChannelEvent
Represents the different events that can be sent through
a channel regarding a Channel’s lifecycle.
Declaration
Swift
public static let heartbeat : String
Declaration
Swift
public static let join : String
Declaration
Swift
public static let leave : String
Declaration
Swift
public static let reply : String
Declaration
Swift
public static let error : String
Declaration
Swift
public static let close : String
================================================
FILE: docs/Structs/Delegated.html
================================================
Delegated Structure Reference
SwiftPhoenixClient Reference
Delegated Structure Reference
Delegated
public struct Delegated < Input , Output >
Provides a memory-safe way of passing callbacks around while not creating
retain cycles. This file was copied from https://github.com/dreymonde/Delegated
instead of added as a dependency to reduce the number of packages that
ship with SwiftPhoenixClient
Declaration
Swift
public mutating func delegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target , Input ) -> Output )
Declaration
Swift
public func call ( _ input : Input ) -> Output ?
Declaration
Swift
public var isDelegateSet : Bool { get }
Declaration
Swift
public mutating func stronglyDelegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target , Input ) -> Output )
Declaration
Swift
public mutating func manuallyDelegate ( with callback : @escaping ( Input ) -> Output )
Declaration
Swift
public mutating func removeDelegate ()
Available where Input == Void
Declaration
Swift
public mutating func delegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target ) -> Output )
Declaration
Swift
public mutating func stronglyDelegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target ) -> Output )
Declaration
Swift
public func call () -> Output ?
Available where Output == Void
Declaration
Swift
public func call ( _ input : Input )
Available where Input == Void, Output == Void
================================================
FILE: docs/Structs.html
================================================
Structures Reference
Structures
The following structures are available globally.
Represents the different events that can be sent through
a channel regarding a Channel’s lifecycle.
See more
Declaration
Swift
public struct ChannelEvent
Provides a memory-safe way of passing callbacks around while not creating
retain cycles. This file was copied from https://github.com/dreymonde/Delegated
instead of added as a dependency to reduce the number of packages that
ship with SwiftPhoenixClient
See more
Declaration
Swift
public struct Delegated < Input , Output >
================================================
FILE: docs/Typealiases.html
================================================
Type Aliases Reference
Type Aliases
The following type aliases are available globally.
Alias for a JSON dictionary [String: Any]
Declaration
Swift
public typealias Payload = [ String : Any ]
Alias for a function returning an optional JSON dictionary (Payload?)
Declaration
Swift
public typealias PayloadClosure = () -> Payload ?
================================================
FILE: docs/css/highlight.css
================================================
/* Credit to https://gist.github.com/wataru420/2048287 */
.highlight {
/* Comment */
/* Error */
/* Keyword */
/* Operator */
/* Comment.Multiline */
/* Comment.Preproc */
/* Comment.Single */
/* Comment.Special */
/* Generic.Deleted */
/* Generic.Deleted.Specific */
/* Generic.Emph */
/* Generic.Error */
/* Generic.Heading */
/* Generic.Inserted */
/* Generic.Inserted.Specific */
/* Generic.Output */
/* Generic.Prompt */
/* Generic.Strong */
/* Generic.Subheading */
/* Generic.Traceback */
/* Keyword.Constant */
/* Keyword.Declaration */
/* Keyword.Pseudo */
/* Keyword.Reserved */
/* Keyword.Type */
/* Literal.Number */
/* Literal.String */
/* Name.Attribute */
/* Name.Builtin */
/* Name.Class */
/* Name.Constant */
/* Name.Entity */
/* Name.Exception */
/* Name.Function */
/* Name.Namespace */
/* Name.Tag */
/* Name.Variable */
/* Operator.Word */
/* Text.Whitespace */
/* Literal.Number.Float */
/* Literal.Number.Hex */
/* Literal.Number.Integer */
/* Literal.Number.Oct */
/* Literal.String.Backtick */
/* Literal.String.Char */
/* Literal.String.Doc */
/* Literal.String.Double */
/* Literal.String.Escape */
/* Literal.String.Heredoc */
/* Literal.String.Interpol */
/* Literal.String.Other */
/* Literal.String.Regex */
/* Literal.String.Single */
/* Literal.String.Symbol */
/* Name.Builtin.Pseudo */
/* Name.Variable.Class */
/* Name.Variable.Global */
/* Name.Variable.Instance */
/* Literal.Number.Integer.Long */ }
.highlight .c {
color: #999988;
font-style: italic; }
.highlight .err {
color: #a61717;
background-color: #e3d2d2; }
.highlight .k {
color: #000000;
font-weight: bold; }
.highlight .o {
color: #000000;
font-weight: bold; }
.highlight .cm {
color: #999988;
font-style: italic; }
.highlight .cp {
color: #999999;
font-weight: bold; }
.highlight .c1 {
color: #999988;
font-style: italic; }
.highlight .cs {
color: #999999;
font-weight: bold;
font-style: italic; }
.highlight .gd {
color: #000000;
background-color: #ffdddd; }
.highlight .gd .x {
color: #000000;
background-color: #ffaaaa; }
.highlight .ge {
color: #000000;
font-style: italic; }
.highlight .gr {
color: #aa0000; }
.highlight .gh {
color: #999999; }
.highlight .gi {
color: #000000;
background-color: #ddffdd; }
.highlight .gi .x {
color: #000000;
background-color: #aaffaa; }
.highlight .go {
color: #888888; }
.highlight .gp {
color: #555555; }
.highlight .gs {
font-weight: bold; }
.highlight .gu {
color: #aaaaaa; }
.highlight .gt {
color: #aa0000; }
.highlight .kc {
color: #000000;
font-weight: bold; }
.highlight .kd {
color: #000000;
font-weight: bold; }
.highlight .kp {
color: #000000;
font-weight: bold; }
.highlight .kr {
color: #000000;
font-weight: bold; }
.highlight .kt {
color: #445588; }
.highlight .m {
color: #009999; }
.highlight .s {
color: #d14; }
.highlight .na {
color: #008080; }
.highlight .nb {
color: #0086B3; }
.highlight .nc {
color: #445588;
font-weight: bold; }
.highlight .no {
color: #008080; }
.highlight .ni {
color: #800080; }
.highlight .ne {
color: #990000;
font-weight: bold; }
.highlight .nf {
color: #990000; }
.highlight .nn {
color: #555555; }
.highlight .nt {
color: #000080; }
.highlight .nv {
color: #008080; }
.highlight .ow {
color: #000000;
font-weight: bold; }
.highlight .w {
color: #bbbbbb; }
.highlight .mf {
color: #009999; }
.highlight .mh {
color: #009999; }
.highlight .mi {
color: #009999; }
.highlight .mo {
color: #009999; }
.highlight .sb {
color: #d14; }
.highlight .sc {
color: #d14; }
.highlight .sd {
color: #d14; }
.highlight .s2 {
color: #d14; }
.highlight .se {
color: #d14; }
.highlight .sh {
color: #d14; }
.highlight .si {
color: #d14; }
.highlight .sx {
color: #d14; }
.highlight .sr {
color: #009926; }
.highlight .s1 {
color: #d14; }
.highlight .ss {
color: #990073; }
.highlight .bp {
color: #999999; }
.highlight .vc {
color: #008080; }
.highlight .vg {
color: #008080; }
.highlight .vi {
color: #008080; }
.highlight .il {
color: #009999; }
================================================
FILE: docs/css/jazzy.css
================================================
html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td {
background: transparent;
border: 0;
margin: 0;
outline: 0;
padding: 0;
vertical-align: baseline; }
body {
background-color: #f2f2f2;
font-family: Helvetica, freesans, Arial, sans-serif;
font-size: 14px;
-webkit-font-smoothing: subpixel-antialiased;
word-wrap: break-word; }
h1, h2, h3 {
margin-top: 0.8em;
margin-bottom: 0.3em;
font-weight: 100;
color: black; }
h1 {
font-size: 2.5em; }
h2 {
font-size: 2em;
border-bottom: 1px solid #e2e2e2; }
h4 {
font-size: 13px;
line-height: 1.5;
margin-top: 21px; }
h5 {
font-size: 1.1em; }
h6 {
font-size: 1.1em;
color: #777; }
.section-name {
color: gray;
display: block;
font-family: Helvetica;
font-size: 22px;
font-weight: 100;
margin-bottom: 15px; }
pre, code {
font: 0.95em Menlo, monospace;
color: #777;
word-wrap: normal; }
p code, li code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px; }
a {
color: #0088cc;
text-decoration: none; }
ul {
padding-left: 15px; }
li {
line-height: 1.8em; }
img {
max-width: 100%; }
blockquote {
margin-left: 0;
padding: 0 10px;
border-left: 4px solid #ccc; }
.content-wrapper {
margin: 0 auto;
width: 980px; }
header {
font-size: 0.85em;
line-height: 26px;
background-color: #414141;
position: fixed;
width: 100%;
z-index: 2; }
header img {
padding-right: 6px;
vertical-align: -4px;
height: 16px; }
header a {
color: #fff; }
header p {
float: left;
color: #999; }
header .header-right {
float: right;
margin-left: 16px; }
#breadcrumbs {
background-color: #f2f2f2;
height: 27px;
padding-top: 17px;
position: fixed;
width: 100%;
z-index: 2;
margin-top: 26px; }
#breadcrumbs #carat {
height: 10px;
margin: 0 5px; }
.sidebar {
background-color: #f9f9f9;
border: 1px solid #e2e2e2;
overflow-y: auto;
overflow-x: hidden;
position: fixed;
top: 70px;
bottom: 0;
width: 230px;
word-wrap: normal; }
.nav-groups {
list-style-type: none;
background: #fff;
padding-left: 0; }
.nav-group-name {
border-bottom: 1px solid #e2e2e2;
font-size: 1.1em;
font-weight: 100;
padding: 15px 0 15px 20px; }
.nav-group-name > a {
color: #333; }
.nav-group-tasks {
margin-top: 5px; }
.nav-group-task {
font-size: 0.9em;
list-style-type: none;
white-space: nowrap; }
.nav-group-task a {
color: #888; }
.main-content {
background-color: #fff;
border: 1px solid #e2e2e2;
margin-left: 246px;
position: absolute;
overflow: hidden;
padding-bottom: 20px;
top: 70px;
width: 734px; }
.main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote {
margin-bottom: 1em; }
.main-content p {
line-height: 1.8em; }
.main-content section .section:first-child {
margin-top: 0;
padding-top: 0; }
.main-content section .task-group-section .task-group:first-of-type {
padding-top: 10px; }
.main-content section .task-group-section .task-group:first-of-type .section-name {
padding-top: 15px; }
.main-content section .heading:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.main-content .section-name p {
margin-bottom: inherit;
line-height: inherit; }
.main-content .section-name code {
background-color: inherit;
padding: inherit;
color: inherit; }
.section {
padding: 0 25px; }
.highlight {
background-color: #eee;
padding: 10px 12px;
border: 1px solid #e2e2e2;
border-radius: 4px;
overflow-x: auto; }
.declaration .highlight {
overflow-x: initial;
padding: 0 40px 40px 0;
margin-bottom: -25px;
background-color: transparent;
border: none; }
.section-name {
margin: 0;
margin-left: 18px; }
.task-group-section {
padding-left: 6px;
border-top: 1px solid #e2e2e2; }
.task-group {
padding-top: 0px; }
.task-name-container a[name]:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.section-name-container {
position: relative;
display: inline-block; }
.section-name-container .section-name-link {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin-bottom: 0; }
.section-name-container .section-name {
position: relative;
pointer-events: none;
z-index: 1; }
.section-name-container .section-name a {
pointer-events: auto; }
.item {
padding-top: 8px;
width: 100%;
list-style-type: none; }
.item a[name]:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.item code {
background-color: transparent;
padding: 0; }
.item .token, .item .direct-link {
padding-left: 3px;
margin-left: 15px;
font-size: 11.9px;
transition: all 300ms; }
.item .token-open {
margin-left: 0px; }
.item .discouraged {
text-decoration: line-through; }
.item .declaration-note {
font-size: .85em;
color: gray;
font-style: italic; }
.pointer-container {
border-bottom: 1px solid #e2e2e2;
left: -23px;
padding-bottom: 13px;
position: relative;
width: 110%; }
.pointer {
background: #f9f9f9;
border-left: 1px solid #e2e2e2;
border-top: 1px solid #e2e2e2;
height: 12px;
left: 21px;
top: -7px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
width: 12px; }
.height-container {
display: none;
left: -25px;
padding: 0 25px;
position: relative;
width: 100%;
overflow: hidden; }
.height-container .section {
background: #f9f9f9;
border-bottom: 1px solid #e2e2e2;
left: -25px;
position: relative;
width: 100%;
padding-top: 10px;
padding-bottom: 5px; }
.aside, .language {
padding: 6px 12px;
margin: 12px 0;
border-left: 5px solid #dddddd;
overflow-y: hidden; }
.aside .aside-title, .language .aside-title {
font-size: 9px;
letter-spacing: 2px;
text-transform: uppercase;
padding-bottom: 0;
margin: 0;
color: #aaa;
-webkit-user-select: none; }
.aside p:last-child, .language p:last-child {
margin-bottom: 0; }
.language {
border-left: 5px solid #cde9f4; }
.language .aside-title {
color: #4b8afb; }
.aside-warning, .aside-deprecated, .aside-unavailable {
border-left: 5px solid #ff6666; }
.aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title {
color: #ff0000; }
.graybox {
border-collapse: collapse;
width: 100%; }
.graybox p {
margin: 0;
word-break: break-word;
min-width: 50px; }
.graybox td {
border: 1px solid #e2e2e2;
padding: 5px 25px 5px 10px;
vertical-align: middle; }
.graybox tr td:first-of-type {
text-align: right;
padding: 7px;
vertical-align: top;
word-break: normal;
width: 40px; }
.slightly-smaller {
font-size: 0.9em; }
#footer {
position: relative;
top: 10px;
bottom: 0px;
margin-left: 25px; }
#footer p {
margin: 0;
color: #aaa;
font-size: 0.8em; }
html.dash header, html.dash #breadcrumbs, html.dash .sidebar {
display: none; }
html.dash .main-content {
width: 980px;
margin-left: 0;
border: none;
width: 100%;
top: 0;
padding-bottom: 0; }
html.dash .height-container {
display: block; }
html.dash .item .token {
margin-left: 0; }
html.dash .content-wrapper {
width: auto; }
html.dash #footer {
position: static; }
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Info.plist
================================================
CFBundleIdentifier
com.jazzy.swiftphoenixclient
CFBundleName
SwiftPhoenixClient
DocSetPlatformFamily
swiftphoenixclient
isDashDocset
dashIndexFilePath
index.html
isJavaScriptEnabled
DashDocSetFamily
dashtoc
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Channel.html
================================================
Channel Class Reference
The topic of the Channel. e.g. “rooms:friends”
Declaration
Swift
public let topic : String
The params sent when joining the channel
Declaration
Swift
public var params : Payload { get set }
Overridable message hook. Receives all events for specialized message
handling before dispatching to the channel callbacks.
return: Must return the message, modified or unmodified
Parameters
msg
The Message received by the client from the server
Declaration
Swift
@discardableResult
public func join ( timeout : TimeInterval ? = nil ) -> Push
Parameters
timeout
Optional. Defaults to Channel’s timeout
Hook into when the Channel is closed. Does not handle retain cycles.
Use delegateOnClose(to:) for automatic handling of retain cycles.
Example:
let channel = socket . channel ( "topic" )
channel . onClose () { [ weak self ] message in
self ? . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func onClose ( _ callback : @escaping (( Message ) -> Void )) -> Int
Parameters
callback
Called when the Channel closes
Hook into when the Channel is closed. Automatically handles retain
cycles. Use onClose() to handle yourself.
Example:
let channel = socket . channel ( "topic" )
channel . delegateOnClose ( to : self ) { ( self , message ) in
self . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOnClose < Target : AnyObject > ( to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Channel closes
Hook into when the Channel receives an Error. Does not handle retain
cycles. Use delegateOnError(to:) for automatic handling of retain
cycles.
Example:
let channel = socket . channel ( "topic" )
channel . onError () { [ weak self ] ( message ) in
self ? . print ( "Channel \( message . topic ) has errored"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func onError ( _ callback : @escaping (( _ message : Message ) -> Void )) -> Int
Parameters
callback
Called when the Channel closes
Hook into when the Channel receives an Error. Automatically handles
retain cycles. Use onError() to handle yourself.
Example:
let channel = socket . channel ( "topic" )
channel . delegateOnError ( to : self ) { ( self , message ) in
self . print ( "Channel \( message . topic ) has closed"
}
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOnError < Target : AnyObject > ( to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Channel closes
Subscribes on channel events. Does not handle retain cycles. Use
delegateOn(_:, to:) for automatic handling of retain cycles.
Subscription returns a ref counter, which can be used later to
unsubscribe the exact event listener
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . on ( "event" ) { [ weak self ] ( message ) in
self ? . print ( "do stuff" )
}
let ref2 = channel . on ( "event" ) { [ weak self ] ( message ) in
self ? . print ( "do other stuff" )
}
channel . off ( "event" , ref1 )
Since unsubscription of ref1, “do stuff” won’t print, but “do other
stuff” will keep on printing on the “event”
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func on ( _ event : String , callback : @escaping (( Message ) -> Void )) -> Int
Parameters
event
callback
Called with the event’s message
Subscribes on channel events. Automatically handles retain cycles. Use
on() to handle yourself.
Subscription returns a ref counter, which can be used later to
unsubscribe the exact event listener
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . delegateOn ( "event" , to : self ) { ( self , message ) in
self ? . print ( "do stuff" )
}
let ref2 = channel . delegateOn ( "event" , to : self ) { ( self , message ) in
self ? . print ( "do other stuff" )
}
channel . off ( "event" , ref1 )
Since unsubscription of ref1, “do stuff” won’t print, but “do other
stuff” will keep on printing on the “event”
return: Ref counter of the subscription. See func off()
Declaration
Swift
@discardableResult
public func delegateOn < Target : AnyObject > ( _ event : String ,
to owner : Target ,
callback : @escaping (( Target , Message ) -> Void )) -> Int
Parameters
event
owner
Class registering the callback. Usually self
callback
Called with the event’s message
Unsubscribes from a channel event. If a ref is given, only the exact
listener will be removed. Else all listeners for the event will be
removed.
Example:
let channel = socket . channel ( "topic" )
let ref1 = channel . on ( "event" ) { _ in print ( "ref1 event" }
let ref2 = channel . on ( "event" ) { _ in print ( "ref2 event" }
let ref3 = channel . on ( "other_event" ) { _ in print ( "ref3 other" }
let ref4 = channel . on ( "other_event" ) { _ in print ( "ref4 other" }
channel . off ( "event" , ref1 )
channel . off ( "other_event" )
After this, only “ref2 event” will be printed if the channel receives
“event” and nothing is printed if the channel receives “other_event”.
paramter ref: Ref counter returned when subscribing. Can be omitted
Declaration
Swift
public func off ( _ event : String , ref : Int ? = nil )
Parameters
event
Event to unsubscribe from
Push a payload to the Channel
Example:
channel
. push ( "event" , payload : [ "message" : "hello" )
. receive ( "ok" ) { _ in { print ( "message sent" ) }
Declaration
Swift
@discardableResult
public func push ( _ event : String ,
payload : Payload ,
timeout : TimeInterval = Defaults . timeoutInterval ) -> Push
Leaves the channel
Unsubscribes from server events, and instructs channel to terminate on
server
Triggers onClose() hooks
To receive leave acknowledgements, use the a receive
hook to bind to the server ack, ie:
Example:
/
channel.leave().receive(“ok”) { _ in { print(“left”) }
return: Push that can add receive hooks
Declaration
Swift
@discardableResult
public func leave ( timeout : TimeInterval = Defaults . timeoutInterval ) -> Push
Overridable message hook. Receives all events for specialized message
handling before dispatching to the channel callbacks.
return: Must return the payload, modified or unmodified
Parameters
event
The event the message was for
payload
The payload for the message
ref
The reference of the message
return: True if the Channel has been closed
Declaration
Swift
public var isClosed : Bool { get }
return: True if the Channel experienced an error
Declaration
Swift
public var isErrored : Bool { get }
return: True if the channel has joined
Declaration
Swift
public var isJoined : Bool { get }
return: True if the channel has requested to join
Declaration
Swift
public var isJoining : Bool { get }
return: True if the channel has requested to leave
Declaration
Swift
public var isLeaving : Bool { get }
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Defaults.html
================================================
Defaults Class Reference
Defaults
A collection of default values and behaviors used accross the Client
Default timeout when sending messages
Declaration
Swift
public static let timeoutInterval : TimeInterval
Default interval to send heartbeats on
Declaration
Swift
public static let heartbeatInterval : TimeInterval
Default reconnect algorithm for the socket
Declaration
Swift
public static let reconnectSteppedBackOff : ( Int ) -> TimeInterval
Default rejoin algorithm for individual channels
Declaration
Swift
public static let rejoinSteppedBackOff : ( Int ) -> TimeInterval
Default encode function, utilizing JSONSerialization.data
Declaration
Swift
public static let encode : ([ String : Any ]) -> Data
Default decode function, utilizing JSONSerialization.jsonObject
Declaration
Swift
public static let decode : ( Data ) -> [ String : Any ]?
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Message.html
================================================
Message Class Reference
Message
Data that is received from the Server.
Reference number. Empty if missing
Declaration
Swift
public let ref : String
Declaration
Swift
public let topic : String
Declaration
Swift
public let event : String
Convenience accessor. Equivalent to getting the status as such:
message . payload [ "status" ]
Declaration
Swift
public var status : String ? { get }
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Presence/Events.html
================================================
Events Enumeration Reference
SwiftPhoenixClient Reference
Events Enumeration Reference
Events
public enum Events : String
Presense Events
Declaration
Swift
case state = "state"
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Presence/Options.html
================================================
Options Structure Reference
SwiftPhoenixClient Reference
Options Structure Reference
Options
Custom options that can be provided when creating Presence
Example:
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Default set of Options used when creating Presence. Uses the
phoenix events “presence_state” and “presence_diff”
Declaration
Swift
public static let defaults : Presence . Options
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Presence.html
================================================
Presence Class Reference
Presence
public final class Presence
The Presence object provides features for syncing presence information from
the server with the client and handling presences joining and leaving.
Syncing state from the server
To sync presence state from the server, first instantiate an object and pass
your channel in to track lifecycle events:
let channel = socket . channel ( "some:topic" )
let presence = Presence ( channel )
If you have custom syncing state events, you can configure the Presence
object to use those instead.
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Next, use the presence.onSync callback to react to state changes from the
server. For example, to render the list of users every time the list
changes, you could write:
presence . onSync { renderUsers ( presence . list ()) }
Listing Presences
presence.list is used to return a list of presence information based on the
local state of metadata. By default, all presence metadata is returned, but
a listBy function can be supplied to allow the client to select which
metadata to use for a given presence. For example, you may have a user
online from different devices with a metadata status of “online”, but they
have set themselves to “away” on another device. In this case, the app may
choose to use the “away” status for what appears on the UI. The example
below defines a listBy function which prioritizes the first metadata which
was registered for each user. This could be the first tab they opened, or
the first device they came online from:
let listBy : ( String , Presence . Map ) -> Presence . Meta = { id , pres in
let first = pres [ "metas" ] !. first !
first [ "count" ] = pres [ "metas" ] !. count
first [ "id" ] = id
return first
}
let onlineUsers = presence . list ( by : listBy )
(NOTE: The underlying behavior is a map on the presence.state. You are
mapping the state dictionary into whatever datastructure suites your needs)
Handling individual presence join and leave events
The presence.onJoin and presence.onLeave callbacks can be used to react to
individual presences joining and leaving the app. For example:
let presence = Presence ( channel )
presence . onJoin { [ weak self ] ( key , current , newPres ) in
if let cur = current {
print ( "user additional presence" , cur )
} else {
print ( "user entered for the first time" , newPres )
}
}
presence . onLeave { [ weak self ] ( key , current , leftPres ) in
if current [ "metas" ]? . isEmpty == true {
print ( "user has left from all devices" , leftPres )
} else {
print ( "user left from a device" , current )
}
}
presence . onSync { renderUsers ( presence . list ()) }
Custom options that can be provided when creating Presence
Example:
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
See more
Declaration
Swift
public struct Options
Declaration
Swift
public enum Events : String
Meta details of a Presence. Just a dictionary of properties
Declaration
Swift
public typealias Meta = [ String : Any ]
A mapping of a String to an array of Metas. e.g. {“metas”: [{id: 1}]}
Declaration
Swift
public typealias Map = [ String : [ Meta ]]
A mapping of a Presence state to a mapping of Metas
Declaration
Swift
public typealias State = [ String : Map ]
Declaration
Swift
public typealias Diff = [ String : State ]
Closure signature of OnJoin callbacks
Declaration
Swift
public typealias OnJoin = ( _ key : String , _ current : Map ?, _ new : Map ) -> Void
Closure signature for OnLeave callbacks
Declaration
Swift
public typealias OnLeave = ( _ key : String , _ current : Map , _ left : Map ) -> Void
/ Closure signature for OnSync callbacks
Declaration
Swift
public typealias OnSync = () -> Void
The state of the Presence
Declaration
Swift
private(set) public var state : State
Pending join and leave diffs that need to be synced
Declaration
Swift
private(set) public var pendingDiffs : [ Diff ]
The channel’s joinRef, set when state events occur
Declaration
Swift
private(set) public var joinRef : String ?
Declaration
Swift
public var isPendingSyncState : Bool { get }
Callback to be informed of joins
Declaration
Swift
public var onJoin : OnJoin { get set }
Declaration
Swift
public func onJoin ( _ callback : @escaping OnJoin )
Callback to be informed of leaves
Declaration
Swift
public var onLeave : OnLeave { get set }
Declaration
Swift
public func onLeave ( _ callback : @escaping OnLeave )
Callback to be informed of synces
Declaration
Swift
public var onSync : OnSync { get set }
Declaration
Swift
public func onSync ( _ callback : @escaping OnSync )
Returns the array of presences, with deault selected metadata.
Declaration
Swift
public func list () -> [ Map ]
Returns the array of presences, with selected metadata
Declaration
Swift
public func list < T > ( by transformer : ( String , Map ) -> T ) -> [ T ]
Filter the Presence state with a given function
Declaration
Swift
public func filter ( by filter : (( String , Map ) -> Bool )?) -> State
Declaration
Swift
@discardableResult
public static func syncState ( _ currentState : State ,
newState : State ,
onJoin : OnJoin = { _ , _ , _ in },
onLeave : OnLeave = { _ , _ , _ in }) -> State
Declaration
Swift
@discardableResult
public static func syncDiff ( _ currentState : State ,
diff : Diff ,
onJoin : OnJoin = { _ , _ , _ in },
onLeave : OnLeave = { _ , _ , _ in }) -> State
Declaration
Swift
public static func filter ( _ presences : State ,
by filter : (( String , Map ) -> Bool )?) -> State
Declaration
Swift
public static func listBy < T > ( _ presences : State ,
transformer : ( String , Map ) -> T ) -> [ T ]
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Push.html
================================================
Push Class Reference
The channel sending the Push
Declaration
Swift
public weak var channel : Channel ?
The event, for example phx_join
Declaration
Swift
public let event : String
The payload, for example [“user_id”: “abc123”]
The push timeout. Default is 10.0 seconds
Declaration
Swift
public var timeout : TimeInterval
Resets and sends the Push
Declaration
Swift
public func resend ( _ timeout : TimeInterval = Defaults . timeoutInterval )
Parameters
timeout
Optional. The push timeout. Default is 10.0s
Sends the Push. If it has already timed out, then the call will
be ignored and return early. Use resend in this case.
Receive a specific event when sending an Outbound message. Subscribing
to status events with this method does not guarantees no retain cycles.
You should pass weak self in the capture list of the callback. You
can call `.delegateReceive(status:, to:, callback:) and the library will
handle it for you.
Example:
channel
. send ( event : "custom" , payload : [ "body" : "example" ])
. receive ( "error" ) { [ weak self ] payload in
print ( "Error: " , payload )
}
Declaration
Swift
@discardableResult
public func receive ( _ status : String ,
callback : @escaping (( Message ) -> ())) -> Push
Parameters
status
callback
Callback to fire when the status is recevied
Receive a specific event when sending an Outbound message. Automatically
prevents retain cycles. See manualReceive(status:, callback:) if you
want to handle this yourself.
Example:
channel
. send ( event : "custom" , payload : [ "body" : "example" ])
. delegateReceive ( "error" , to : self ) { payload in
print ( "Error: " , payload )
}
Declaration
Swift
@discardableResult
public func delegateReceive < Target : AnyObject > ( _ status : String ,
to owner : Target ,
callback : @escaping (( Target , Message ) -> ())) -> Push
Parameters
status
owner
The class that is calling .receive. Usually self
callback
Callback to fire when the status is recevied
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes/Socket.html
================================================
Socket Class Reference
Socket
public class Socket
extension Socket : WebSocketDelegate
Socket Connection
A single connection is established to the server and
channels are multiplexed over the connection.
Connect to the server using the Socket class:
let socket = new Socket ( "/socket" , paramsClosure : { [ "userToken" : "123" ] })
socket . connect ()
The Socket constructor takes the mount point of the socket,
the authentication params, as well as options that can be found in
the Socket docs, such as configuring the heartbeat.
The string WebSocket endpoint (ie "ws://example.com/socket",
"wss://example.com", etc.) That was passed to the Socket during
initialization. The URL endpoint will be modified by the Socket to
include "/websocket" if missing.
Declaration
Swift
public let endPoint : String
The fully qualified socket URL
Declaration
Swift
public private(set) var endPointUrl : URL
Resolves to return the paramsClosure result at the time of calling.
If the Socket was created with static params, then those will be
returned every time.
Declaration
Swift
public var params : Payload ? { get }
The optional params closure used to get params whhen connecting. Must
be set when initializaing the Socket.
Override to provide custom encoding of data before writing to the socket
Declaration
Swift
public var encode : ([ String : Any ]) -> Data
Override to provide customd decoding of data read from the socket
Declaration
Swift
public var decode : ( Data ) -> [ String : Any ]?
Timeout to use when opening connections
Declaration
Swift
public var timeout : TimeInterval
Interval between sending a heartbeat
Declaration
Swift
public var heartbeatInterval : TimeInterval
Interval between socket reconnect attempts, in seconds
Declaration
Swift
public var reconnectAfter : ( Int ) -> TimeInterval
Interval between channel rejoin attempts, in seconds
Declaration
Swift
public var rejoinAfter : ( Int ) -> TimeInterval
The optional function to receive logs
Declaration
Swift
public var logger : (( String ) -> Void )?
Disables heartbeats from being sent. Default is false.
Declaration
Swift
public var skipHeartbeat : Bool
Enable/Disable SSL certificate validation. Default is false. This
must be set before calling socket.connect() in order to be applied
Declaration
Swift
public var disableSSLCertValidation : Bool
Configure custom SSL validation logic, eg. SSL pinning. This
must be set before calling socket.connect() in order to apply.
Declaration
Swift
public var security : SSLTrustValidator ?
Configure the encryption used by your client by setting the
allowed cipher suites supported by your server. This must be
set before calling socket.connect() in order to apply.
Declaration
Swift
public var enabledSSLCipherSuites : [ SSLCipherSuite ]?
Declaration
Swift
public convenience init ( _ endPoint : String ,
params : Payload ? = nil )
Declaration
Swift
public convenience init ( _ endPoint : String ,
paramsClosure : PayloadClosure ?)
return: The socket protocol, wss or ws
Declaration
Swift
public var websocketProtocol : String { get }
return: True if the socket is connected
Declaration
Swift
public var isConnected : Bool { get }
Connects the Socket. The params passed to the Socket on initialization
will be sent through the connection. If the Socket is already connected,
then this call will be ignored.
Declaration
Swift
public func connect ()
Disconnects the socket
paramter callback: Optional. Called when disconnected
Declaration
Swift
public func disconnect ( code : CloseCode = CloseCode . normal ,
callback : (() -> Void )? = nil )
Parameters
code
Optional. Closing status code
Register Socket State Callbacks
Registers callbacks for connection open events. Does not handle retain
cycles. Use delegateOnOpen(to:) for automatic handling of retain cycles.
Example:
socket . onOpen () { [ weak self ] in
self ? . print ( "Socket Connection Open" )
}
Declaration
Swift
public func onOpen ( callback : @escaping () -> Void )
Parameters
callback
Called when the Socket is opened
Registers callbacks for connection open events. Automatically handles
retain cycles. Use onOpen() to handle yourself.
Example:
socket . delegateOnOpen ( to : self ) { self in
self . print ( "Socket Connection Open" )
}
Declaration
Swift
public func delegateOnOpen < T : AnyObject > ( to owner : T ,
callback : @escaping (( T ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket is opened
Registers callbacks for connection close events. Does not handle retain
cycles. Use delegateOnClose(_:) for automatic handling of retain cycles.
Example:
socket . onClose () { [ weak self ] in
self ? . print ( "Socket Connection Close" )
}
Declaration
Swift
public func onClose ( callback : @escaping () -> Void )
Parameters
callback
Called when the Socket is closed
Registers callbacks for connection close events. Automatically handles
retain cycles. Use onClose() to handle yourself.
Example:
socket . delegateOnClose ( self ) { self in
self . print ( "Socket Connection Close" )
}
Declaration
Swift
public func delegateOnClose < T : AnyObject > ( to owner : T ,
callback : @escaping (( T ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket is closed
Registers callbacks for connection error events. Does not handle retain
cycles. Use delegateOnError(to:) for automatic handling of retain cycles.
Example:
socket . onError () { [ weak self ] ( error ) in
self ? . print ( "Socket Connection Error" , error )
}
Declaration
Swift
public func onError ( callback : @escaping ( Error ) -> Void )
Parameters
callback
Called when the Socket errors
Registers callbacks for connection error events. Automatically handles
retain cycles. Use manualOnError() to handle yourself.
Example:
socket . delegateOnError ( to : self ) { ( self , error ) in
self . print ( "Socket Connection Error" , error )
}
Declaration
Swift
public func delegateOnError < T : AnyObject > ( to owner : T ,
callback : @escaping (( T , Error ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket errors
Registers callbacks for connection message events. Does not handle
retain cycles. Use delegateOnMessage(_to:) for automatic handling of
retain cycles.
Example:
socket . onMessage () { [ weak self ] ( message ) in
self ? . print ( "Socket Connection Message" , message )
}
Declaration
Swift
public func onMessage ( callback : @escaping ( Message ) -> Void )
Parameters
callback
Called when the Socket receives a message event
Registers callbacks for connection message events. Automatically handles
retain cycles. Use onMessage() to handle yourself.
Example:
socket . delegateOnMessage ( self ) { ( self , message ) in
self . print ( "Socket Connection Message" , message )
}
Declaration
Swift
public func delegateOnMessage < T : AnyObject > ( to owner : T ,
callback : @escaping (( T , Message ) -> Void ))
Parameters
owner
Class registering the callback. Usually self
callback
Called when the Socket receives a message event
Releases all stored callback hooks (onError, onOpen, onClose, etc.) You should
call this method when you are finished when the Socket in order to release
any references held by the socket.
Declaration
Swift
public func releaseCallbacks ()
Initialize a new Channel
Example:
let channel = socket . channel ( "rooms" , params : [ "user_id" : "abc123" ])
Declaration
Swift
public func channel ( _ topic : String ,
params : [ String : Any ] = [:]) -> Channel
Parameters
topic
params
Optional. Parameters for the channel
Removes the Channel from the socket. This does not cause the channel to
inform the server that it is leaving. You should call channel.leave()
prior to removing the Channel.
Example:
channel . leave ()
socket . remove ( channel )
Declaration
Swift
public func remove ( _ channel : Channel )
return: the next message ref, accounting for overflows
Declaration
Swift
public func makeRef () -> String
Declaration
Swift
public func websocketDidConnect ( socket : WebSocketClient )
Declaration
Swift
public func websocketDidDisconnect ( socket : WebSocketClient , error : Error ?)
Declaration
Swift
public func websocketDidReceiveMessage ( socket : WebSocketClient , text : String )
Declaration
Swift
public func websocketDidReceiveData ( socket : WebSocketClient , data : Data )
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Classes.html
================================================
Classes Reference
Classes
The following classes are available globally.
Declaration
Swift
public class Channel
Data that is received from the Server.
See more
Declaration
Swift
public class Message
The Presence object provides features for syncing presence information from
the server with the client and handling presences joining and leaving.
Syncing state from the server
To sync presence state from the server, first instantiate an object and pass
your channel in to track lifecycle events:
let channel = socket . channel ( "some:topic" )
let presence = Presence ( channel )
If you have custom syncing state events, you can configure the Presence
object to use those instead.
let options = Options ( events : [ . state : "my_state" , . diff : "my_diff" ])
let presence = Presence ( channel , opts : options )
Next, use the presence.onSync callback to react to state changes from the
server. For example, to render the list of users every time the list
changes, you could write:
presence . onSync { renderUsers ( presence . list ()) }
Listing Presences
presence.list is used to return a list of presence information based on the
local state of metadata. By default, all presence metadata is returned, but
a listBy function can be supplied to allow the client to select which
metadata to use for a given presence. For example, you may have a user
online from different devices with a metadata status of “online”, but they
have set themselves to “away” on another device. In this case, the app may
choose to use the “away” status for what appears on the UI. The example
below defines a listBy function which prioritizes the first metadata which
was registered for each user. This could be the first tab they opened, or
the first device they came online from:
let listBy : ( String , Presence . Map ) -> Presence . Meta = { id , pres in
let first = pres [ "metas" ] !. first !
first [ "count" ] = pres [ "metas" ] !. count
first [ "id" ] = id
return first
}
let onlineUsers = presence . list ( by : listBy )
(NOTE: The underlying behavior is a map on the presence.state. You are
mapping the state dictionary into whatever datastructure suites your needs)
Handling individual presence join and leave events
The presence.onJoin and presence.onLeave callbacks can be used to react to
individual presences joining and leaving the app. For example:
let presence = Presence ( channel )
presence . onJoin { [ weak self ] ( key , current , newPres ) in
if let cur = current {
print ( "user additional presence" , cur )
} else {
print ( "user entered for the first time" , newPres )
}
}
presence . onLeave { [ weak self ] ( key , current , leftPres ) in
if current [ "metas" ]? . isEmpty == true {
print ( "user has left from all devices" , leftPres )
} else {
print ( "user left from a device" , current )
}
}
presence . onSync { renderUsers ( presence . list ()) }
See more
Declaration
Swift
public final class Presence
Socket Connection
A single connection is established to the server and
channels are multiplexed over the connection.
Connect to the server using the Socket class:
let socket = new Socket ( "/socket" , paramsClosure : { [ "userToken" : "123" ] })
socket . connect ()
The Socket constructor takes the mount point of the socket,
the authentication params, as well as options that can be found in
the Socket docs, such as configuring the heartbeat.
See more
Declaration
Swift
public class Socket
extension Socket : WebSocketDelegate
A collection of default values and behaviors used accross the Client
See more
Declaration
Swift
public class Defaults
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Enums/ChannelState.html
================================================
ChannelState Enumeration Reference
SwiftPhoenixClient Reference
ChannelState Enumeration Reference
ChannelState
public enum ChannelState : String
Represents the multiple states that a Channel can be in
throughout it’s lifecycle.
Declaration
Swift
case closed = "closed"
Declaration
Swift
case errored = "errored"
Declaration
Swift
case joined = "joined"
Declaration
Swift
case joining = "joining"
Declaration
Swift
case leaving = "leaving"
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Enums.html
================================================
Enumerations Reference
Enumerations
The following enumerations are available globally.
Represents the multiple states that a Channel can be in
throughout it’s lifecycle.
See more
Declaration
Swift
public enum ChannelState : String
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Global Variables.html
================================================
Global Variables Reference
Global Variables
The following global variables are available globally.
Default timeout when making a connection set to 10 seconds
Declaration
Swift
public let PHOENIX_DEFAULT_TIMEOUT : Int
Declaration
Swift
public let PHOENIX_TIMEOUT_INTERVAL : TimeInterval
Default heartbeat interval set to 30 seconds
Declaration
Swift
public let PHOENIX_DEFAULT_HEARTBEAT : Int
Default heartbeat interval set to 30.0 seconds
Declaration
Swift
public let PHOENIX_HEARTBEAT_INTERVAL : TimeInterval
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Protocols/Serializer.html
================================================
Serializer Protocol Reference
SwiftPhoenixClient Reference
Serializer Protocol Reference
Serializer
public protocol Serializer
Provides customization when enoding and decoding data within the Socket
Convert a message into Data to be sent over the Socket
Declaration
Swift
func encode ( _ message : [ String : Any ]) throws -> Data
Convert data from the Socket into a Message
Declaration
Swift
func decode ( _ data : Data ) -> Message ?
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Protocols.html
================================================
Protocols Reference
Protocols
The following protocols are available globally.
Provides customization when enoding and decoding data within the Socket
See more
Declaration
Swift
public protocol Serializer
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Structs/ChannelEvent.html
================================================
ChannelEvent Structure Reference
SwiftPhoenixClient Reference
ChannelEvent Structure Reference
ChannelEvent
public struct ChannelEvent
Represents the different events that can be sent through
a channel regarding a Channel’s lifecycle.
Declaration
Swift
public static let heartbeat : String
Declaration
Swift
public static let join : String
Declaration
Swift
public static let leave : String
Declaration
Swift
public static let reply : String
Declaration
Swift
public static let error : String
Declaration
Swift
public static let close : String
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Structs/Delegated.html
================================================
Delegated Structure Reference
SwiftPhoenixClient Reference
Delegated Structure Reference
Delegated
public struct Delegated < Input , Output >
Provides a memory-safe way of passing callbacks around while not creating
retain cycles. This file was copied from https://github.com/dreymonde/Delegated
instead of added as a dependency to reduce the number of packages that
ship with SwiftPhoenixClient
Declaration
Swift
public mutating func delegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target , Input ) -> Output )
Declaration
Swift
public func call ( _ input : Input ) -> Output ?
Declaration
Swift
public var isDelegateSet : Bool { get }
Declaration
Swift
public mutating func stronglyDelegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target , Input ) -> Output )
Declaration
Swift
public mutating func manuallyDelegate ( with callback : @escaping ( Input ) -> Output )
Declaration
Swift
public mutating func removeDelegate ()
Available where Input == Void
Declaration
Swift
public mutating func delegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target ) -> Output )
Declaration
Swift
public mutating func stronglyDelegate < Target : AnyObject > ( to target : Target ,
with callback : @escaping ( Target ) -> Output )
Declaration
Swift
public func call () -> Output ?
Available where Output == Void
Declaration
Swift
public func call ( _ input : Input )
Available where Input == Void, Output == Void
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Structs.html
================================================
Structures Reference
Structures
The following structures are available globally.
Represents the different events that can be sent through
a channel regarding a Channel’s lifecycle.
See more
Declaration
Swift
public struct ChannelEvent
Provides a memory-safe way of passing callbacks around while not creating
retain cycles. This file was copied from https://github.com/dreymonde/Delegated
instead of added as a dependency to reduce the number of packages that
ship with SwiftPhoenixClient
See more
Declaration
Swift
public struct Delegated < Input , Output >
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/Typealiases.html
================================================
Type Aliases Reference
Type Aliases
The following type aliases are available globally.
Alias for a JSON dictionary [String: Any]
Declaration
Swift
public typealias Payload = [ String : Any ]
Alias for a function returning an optional JSON dictionary (Payload?)
Declaration
Swift
public typealias PayloadClosure = () -> Payload ?
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/css/highlight.css
================================================
/* Credit to https://gist.github.com/wataru420/2048287 */
.highlight {
/* Comment */
/* Error */
/* Keyword */
/* Operator */
/* Comment.Multiline */
/* Comment.Preproc */
/* Comment.Single */
/* Comment.Special */
/* Generic.Deleted */
/* Generic.Deleted.Specific */
/* Generic.Emph */
/* Generic.Error */
/* Generic.Heading */
/* Generic.Inserted */
/* Generic.Inserted.Specific */
/* Generic.Output */
/* Generic.Prompt */
/* Generic.Strong */
/* Generic.Subheading */
/* Generic.Traceback */
/* Keyword.Constant */
/* Keyword.Declaration */
/* Keyword.Pseudo */
/* Keyword.Reserved */
/* Keyword.Type */
/* Literal.Number */
/* Literal.String */
/* Name.Attribute */
/* Name.Builtin */
/* Name.Class */
/* Name.Constant */
/* Name.Entity */
/* Name.Exception */
/* Name.Function */
/* Name.Namespace */
/* Name.Tag */
/* Name.Variable */
/* Operator.Word */
/* Text.Whitespace */
/* Literal.Number.Float */
/* Literal.Number.Hex */
/* Literal.Number.Integer */
/* Literal.Number.Oct */
/* Literal.String.Backtick */
/* Literal.String.Char */
/* Literal.String.Doc */
/* Literal.String.Double */
/* Literal.String.Escape */
/* Literal.String.Heredoc */
/* Literal.String.Interpol */
/* Literal.String.Other */
/* Literal.String.Regex */
/* Literal.String.Single */
/* Literal.String.Symbol */
/* Name.Builtin.Pseudo */
/* Name.Variable.Class */
/* Name.Variable.Global */
/* Name.Variable.Instance */
/* Literal.Number.Integer.Long */ }
.highlight .c {
color: #999988;
font-style: italic; }
.highlight .err {
color: #a61717;
background-color: #e3d2d2; }
.highlight .k {
color: #000000;
font-weight: bold; }
.highlight .o {
color: #000000;
font-weight: bold; }
.highlight .cm {
color: #999988;
font-style: italic; }
.highlight .cp {
color: #999999;
font-weight: bold; }
.highlight .c1 {
color: #999988;
font-style: italic; }
.highlight .cs {
color: #999999;
font-weight: bold;
font-style: italic; }
.highlight .gd {
color: #000000;
background-color: #ffdddd; }
.highlight .gd .x {
color: #000000;
background-color: #ffaaaa; }
.highlight .ge {
color: #000000;
font-style: italic; }
.highlight .gr {
color: #aa0000; }
.highlight .gh {
color: #999999; }
.highlight .gi {
color: #000000;
background-color: #ddffdd; }
.highlight .gi .x {
color: #000000;
background-color: #aaffaa; }
.highlight .go {
color: #888888; }
.highlight .gp {
color: #555555; }
.highlight .gs {
font-weight: bold; }
.highlight .gu {
color: #aaaaaa; }
.highlight .gt {
color: #aa0000; }
.highlight .kc {
color: #000000;
font-weight: bold; }
.highlight .kd {
color: #000000;
font-weight: bold; }
.highlight .kp {
color: #000000;
font-weight: bold; }
.highlight .kr {
color: #000000;
font-weight: bold; }
.highlight .kt {
color: #445588; }
.highlight .m {
color: #009999; }
.highlight .s {
color: #d14; }
.highlight .na {
color: #008080; }
.highlight .nb {
color: #0086B3; }
.highlight .nc {
color: #445588;
font-weight: bold; }
.highlight .no {
color: #008080; }
.highlight .ni {
color: #800080; }
.highlight .ne {
color: #990000;
font-weight: bold; }
.highlight .nf {
color: #990000; }
.highlight .nn {
color: #555555; }
.highlight .nt {
color: #000080; }
.highlight .nv {
color: #008080; }
.highlight .ow {
color: #000000;
font-weight: bold; }
.highlight .w {
color: #bbbbbb; }
.highlight .mf {
color: #009999; }
.highlight .mh {
color: #009999; }
.highlight .mi {
color: #009999; }
.highlight .mo {
color: #009999; }
.highlight .sb {
color: #d14; }
.highlight .sc {
color: #d14; }
.highlight .sd {
color: #d14; }
.highlight .s2 {
color: #d14; }
.highlight .se {
color: #d14; }
.highlight .sh {
color: #d14; }
.highlight .si {
color: #d14; }
.highlight .sx {
color: #d14; }
.highlight .sr {
color: #009926; }
.highlight .s1 {
color: #d14; }
.highlight .ss {
color: #990073; }
.highlight .bp {
color: #999999; }
.highlight .vc {
color: #008080; }
.highlight .vg {
color: #008080; }
.highlight .vi {
color: #008080; }
.highlight .il {
color: #009999; }
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/css/jazzy.css
================================================
html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td {
background: transparent;
border: 0;
margin: 0;
outline: 0;
padding: 0;
vertical-align: baseline; }
body {
background-color: #f2f2f2;
font-family: Helvetica, freesans, Arial, sans-serif;
font-size: 14px;
-webkit-font-smoothing: subpixel-antialiased;
word-wrap: break-word; }
h1, h2, h3 {
margin-top: 0.8em;
margin-bottom: 0.3em;
font-weight: 100;
color: black; }
h1 {
font-size: 2.5em; }
h2 {
font-size: 2em;
border-bottom: 1px solid #e2e2e2; }
h4 {
font-size: 13px;
line-height: 1.5;
margin-top: 21px; }
h5 {
font-size: 1.1em; }
h6 {
font-size: 1.1em;
color: #777; }
.section-name {
color: gray;
display: block;
font-family: Helvetica;
font-size: 22px;
font-weight: 100;
margin-bottom: 15px; }
pre, code {
font: 0.95em Menlo, monospace;
color: #777;
word-wrap: normal; }
p code, li code {
background-color: #eee;
padding: 2px 4px;
border-radius: 4px; }
a {
color: #0088cc;
text-decoration: none; }
ul {
padding-left: 15px; }
li {
line-height: 1.8em; }
img {
max-width: 100%; }
blockquote {
margin-left: 0;
padding: 0 10px;
border-left: 4px solid #ccc; }
.content-wrapper {
margin: 0 auto;
width: 980px; }
header {
font-size: 0.85em;
line-height: 26px;
background-color: #414141;
position: fixed;
width: 100%;
z-index: 2; }
header img {
padding-right: 6px;
vertical-align: -4px;
height: 16px; }
header a {
color: #fff; }
header p {
float: left;
color: #999; }
header .header-right {
float: right;
margin-left: 16px; }
#breadcrumbs {
background-color: #f2f2f2;
height: 27px;
padding-top: 17px;
position: fixed;
width: 100%;
z-index: 2;
margin-top: 26px; }
#breadcrumbs #carat {
height: 10px;
margin: 0 5px; }
.sidebar {
background-color: #f9f9f9;
border: 1px solid #e2e2e2;
overflow-y: auto;
overflow-x: hidden;
position: fixed;
top: 70px;
bottom: 0;
width: 230px;
word-wrap: normal; }
.nav-groups {
list-style-type: none;
background: #fff;
padding-left: 0; }
.nav-group-name {
border-bottom: 1px solid #e2e2e2;
font-size: 1.1em;
font-weight: 100;
padding: 15px 0 15px 20px; }
.nav-group-name > a {
color: #333; }
.nav-group-tasks {
margin-top: 5px; }
.nav-group-task {
font-size: 0.9em;
list-style-type: none;
white-space: nowrap; }
.nav-group-task a {
color: #888; }
.main-content {
background-color: #fff;
border: 1px solid #e2e2e2;
margin-left: 246px;
position: absolute;
overflow: hidden;
padding-bottom: 20px;
top: 70px;
width: 734px; }
.main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote {
margin-bottom: 1em; }
.main-content p {
line-height: 1.8em; }
.main-content section .section:first-child {
margin-top: 0;
padding-top: 0; }
.main-content section .task-group-section .task-group:first-of-type {
padding-top: 10px; }
.main-content section .task-group-section .task-group:first-of-type .section-name {
padding-top: 15px; }
.main-content section .heading:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.main-content .section-name p {
margin-bottom: inherit;
line-height: inherit; }
.main-content .section-name code {
background-color: inherit;
padding: inherit;
color: inherit; }
.section {
padding: 0 25px; }
.highlight {
background-color: #eee;
padding: 10px 12px;
border: 1px solid #e2e2e2;
border-radius: 4px;
overflow-x: auto; }
.declaration .highlight {
overflow-x: initial;
padding: 0 40px 40px 0;
margin-bottom: -25px;
background-color: transparent;
border: none; }
.section-name {
margin: 0;
margin-left: 18px; }
.task-group-section {
padding-left: 6px;
border-top: 1px solid #e2e2e2; }
.task-group {
padding-top: 0px; }
.task-name-container a[name]:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.section-name-container {
position: relative;
display: inline-block; }
.section-name-container .section-name-link {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin-bottom: 0; }
.section-name-container .section-name {
position: relative;
pointer-events: none;
z-index: 1; }
.section-name-container .section-name a {
pointer-events: auto; }
.item {
padding-top: 8px;
width: 100%;
list-style-type: none; }
.item a[name]:before {
content: "";
display: block;
padding-top: 70px;
margin: -70px 0 0; }
.item code {
background-color: transparent;
padding: 0; }
.item .token, .item .direct-link {
padding-left: 3px;
margin-left: 15px;
font-size: 11.9px;
transition: all 300ms; }
.item .token-open {
margin-left: 0px; }
.item .discouraged {
text-decoration: line-through; }
.item .declaration-note {
font-size: .85em;
color: gray;
font-style: italic; }
.pointer-container {
border-bottom: 1px solid #e2e2e2;
left: -23px;
padding-bottom: 13px;
position: relative;
width: 110%; }
.pointer {
background: #f9f9f9;
border-left: 1px solid #e2e2e2;
border-top: 1px solid #e2e2e2;
height: 12px;
left: 21px;
top: -7px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
width: 12px; }
.height-container {
display: none;
left: -25px;
padding: 0 25px;
position: relative;
width: 100%;
overflow: hidden; }
.height-container .section {
background: #f9f9f9;
border-bottom: 1px solid #e2e2e2;
left: -25px;
position: relative;
width: 100%;
padding-top: 10px;
padding-bottom: 5px; }
.aside, .language {
padding: 6px 12px;
margin: 12px 0;
border-left: 5px solid #dddddd;
overflow-y: hidden; }
.aside .aside-title, .language .aside-title {
font-size: 9px;
letter-spacing: 2px;
text-transform: uppercase;
padding-bottom: 0;
margin: 0;
color: #aaa;
-webkit-user-select: none; }
.aside p:last-child, .language p:last-child {
margin-bottom: 0; }
.language {
border-left: 5px solid #cde9f4; }
.language .aside-title {
color: #4b8afb; }
.aside-warning, .aside-deprecated, .aside-unavailable {
border-left: 5px solid #ff6666; }
.aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title {
color: #ff0000; }
.graybox {
border-collapse: collapse;
width: 100%; }
.graybox p {
margin: 0;
word-break: break-word;
min-width: 50px; }
.graybox td {
border: 1px solid #e2e2e2;
padding: 5px 25px 5px 10px;
vertical-align: middle; }
.graybox tr td:first-of-type {
text-align: right;
padding: 7px;
vertical-align: top;
word-break: normal;
width: 40px; }
.slightly-smaller {
font-size: 0.9em; }
#footer {
position: relative;
top: 10px;
bottom: 0px;
margin-left: 25px; }
#footer p {
margin: 0;
color: #aaa;
font-size: 0.8em; }
html.dash header, html.dash #breadcrumbs, html.dash .sidebar {
display: none; }
html.dash .main-content {
width: 980px;
margin-left: 0;
border: none;
width: 100%;
top: 0;
padding-bottom: 0; }
html.dash .height-container {
display: block; }
html.dash .item .token {
margin-left: 0; }
html.dash .content-wrapper {
width: auto; }
html.dash #footer {
position: static; }
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/index.html
================================================
SwiftPhoenixClient Reference
SwiftPhoenixClient Reference
SwiftPhoenixClient Reference
Swift Phoenix Client
About
Swift Phoenix Client is an extension of Starscream websocket client library
that makes it easy to connect to Phoenix sockets in a similar manner to the
phoenix.js client.
The client is currently updated to mirror phoenix.js 1.4.
Swift Versions
master currently supports Swift 5.0. You’ll need to set your target to = 1.0.1 if your project is using Swift 4.2
swift
client
4.2
1.0.1
5.0
1.1.0
Installation
CocoaPods
You can install SwiftPhoenix Client via CocoaPods by adding the following to your
Podfile. Keep in mind that in order to use Swift Phoenix Client, the minimum iOS
target must be ‘9.0’
platform :ios, '9.0'
use_frameworks!
pod "SwiftPhoenixClient", '~> 1.0'
and running pod install. From there you will need to add import SwiftPhoenixClient in any class you want it to be used.
Carthage
If you use Carthage to manage your dependencies, simply add
SwiftPhoenixClient to your Cartfile:
github "davidstump/SwiftPhoenixClient" ~> 1.0
SwiftPackageManager
SwiftPackageManager is properly supported starting in SwiftPhoenixClient v1.2.0. You can add the following to your Package.swift
.package(url: "https://github.com/davidstump/SwiftPhoenixClient.git", .upToNextMajor(from: "1.2.0"))
Make sure you have added SwiftPhoenixClient.framework, and Starscream.framework to the “Linked Frameworks and Libraries ” section of your target, and have included them in your Carthage framework copying build phase.
Usage
Using the Swift Phoenix Client is extremely easy (and familiar if have used the phoenix.s client).
See the Usage Guide for details instructions. You can also check out the documentation
Example
Check out the ViewController in this repo for a brief example of a simple iOS chat application using the Phoenix Chat Example
Also check out both the Swift and Elixir channels on IRC.
Development
Check out the wiki page for getting started
Tested with the Phoenix Chat Server example , upgraded to Phoenix 1.2.
Thanks
Many many thanks to Daniel Rees for his many contributions and continued maintenance of this project!
License
SwiftPhoenixClient is available under the MIT license. See the LICENSE file for more info.
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/js/jazzy.js
================================================
window.jazzy = {'docset': false}
if (typeof window.dash != 'undefined') {
document.documentElement.className += ' dash'
window.jazzy.docset = true
}
if (navigator.userAgent.match(/xcode/i)) {
document.documentElement.className += ' xcode'
window.jazzy.docset = true
}
function toggleItem($link, $content) {
var animationDuration = 300;
$link.toggleClass('token-open');
$content.slideToggle(animationDuration);
}
function itemLinkToContent($link) {
return $link.parent().parent().next();
}
// On doc load + hash-change, open any targetted item
function openCurrentItemIfClosed() {
if (window.jazzy.docset) {
return;
}
var $link = $(`.token[href="${location.hash}"]`);
$content = itemLinkToContent($link);
if ($content.is(':hidden')) {
toggleItem($link, $content);
}
}
$(openCurrentItemIfClosed);
$(window).on('hashchange', openCurrentItemIfClosed);
// On item link ('token') click, toggle its discussion
$('.token').on('click', function(event) {
if (window.jazzy.docset) {
return;
}
var $link = $(this);
toggleItem($link, itemLinkToContent($link));
// Keeps the document from jumping to the hash.
var href = $link.attr('href');
if (history.pushState) {
history.pushState({}, '', href);
} else {
location.hash = href;
}
event.preventDefault();
});
// Clicks on links to the current, closed, item need to open the item
$("a:not('.token')").on('click', function() {
if (location == this.href) {
openCurrentItemIfClosed();
}
});
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/search.json
================================================
{"Typealiases.html#/s:18SwiftPhoenixClient7Payloada":{"name":"Payload","abstract":"Alias for a JSON dictionary [String: Any]
"},"Typealiases.html#/s:18SwiftPhoenixClient14PayloadClosurea":{"name":"PayloadClosure","abstract":"Alias for a function returning an optional JSON dictionary (Payload?)
"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVACyxq_Gycfc":{"name":"init()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV8delegate2to4withyqd___q_qd___xtctRld__ClF":{"name":"delegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV4callyq_SgxF":{"name":"call(_:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV13isDelegateSetSbvp":{"name":"isDelegateSet","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV16stronglyDelegate2to4withyqd___q_qd___xtctRld__ClF":{"name":"stronglyDelegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV16manuallyDelegate4withyq_xc_tF":{"name":"manuallyDelegate(with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV14removeDelegateyyF":{"name":"removeDelegate()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE8delegate2to4withyqd___q_qd__ctRld__ClF":{"name":"delegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE16stronglyDelegate2to4withyqd___q_qd__ctRld__ClF":{"name":"stronglyDelegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE4callq_SgyF":{"name":"call()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRs_rlE4callyyxF":{"name":"call(_:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszytRs_rlE4callyyF":{"name":"call()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV9heartbeatSSvpZ":{"name":"heartbeat","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV4joinSSvpZ":{"name":"join","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5leaveSSvpZ":{"name":"leave","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5replySSvpZ":{"name":"reply","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5errorSSvpZ":{"name":"error","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5closeSSvpZ":{"name":"close","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html":{"name":"ChannelEvent","abstract":"Represents the different events that can be sent through"},"Structs/Delegated.html":{"name":"Delegated","abstract":"
Provides a memory-safe way of passing callbacks around while not creating"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO6closedyA2CmF":{"name":"closed","abstract":"
Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7erroredyA2CmF":{"name":"errored","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO6joinedyA2CmF":{"name":"joined","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7joiningyA2CmF":{"name":"joining","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7leavingyA2CmF":{"name":"leaving","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html":{"name":"ChannelState","abstract":"Represents the multiple states that a Channel can be in"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC15timeoutIntervalSdvpZ":{"name":"timeoutInterval","abstract":"
Default timeout when sending messages
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC17heartbeatIntervalSdvpZ":{"name":"heartbeatInterval","abstract":"Default interval to send heartbeats on
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC23reconnectSteppedBackOffySdSicvpZ":{"name":"reconnectSteppedBackOff","abstract":"Default reconnect algorithm for the socket
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC20rejoinSteppedBackOffySdSicvpZ":{"name":"rejoinSteppedBackOff","abstract":"Default rejoin algorithm for individual channels
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC6encodey10Foundation4DataVSDySSypGcvpZ":{"name":"encode","abstract":"Default encode function, utilizing JSONSerialization.data
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC6decodeySDySSypGSg10Foundation4DataVcvpZ":{"name":"decode","abstract":"Default decode function, utilizing JSONSerialization.jsonObject
","parent_name":"Defaults"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC8endPointSSvp":{"name":"endPoint","abstract":"The string WebSocket endpoint (ie "ws://example.com/socket",","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11endPointUrl10Foundation3URLVvp":{"name":"endPointUrl","abstract":"
The fully qualified socket URL
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6paramsSDySSypGSgvp":{"name":"params","abstract":"Resolves to return the paramsClosure result at the time of calling.","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC13paramsClosureSDySSypGSgycSgvp":{"name":"paramsClosure","abstract":"
The optional params closure used to get params whhen connecting. Must","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6encodey10Foundation4DataVSDySSypGcvp":{"name":"encode","abstract":"
Override to provide custom encoding of data before writing to the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6decodeySDySSypGSg10Foundation4DataVcvp":{"name":"decode","abstract":"Override to provide customd decoding of data read from the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7timeoutSdvp":{"name":"timeout","abstract":"Timeout to use when opening connections
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17heartbeatIntervalSdvp":{"name":"heartbeatInterval","abstract":"Interval between sending a heartbeat
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC14reconnectAfterySdSicvp":{"name":"reconnectAfter","abstract":"Interval between socket reconnect attempts, in seconds
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11rejoinAfterySdSicvp":{"name":"rejoinAfter","abstract":"Interval between channel rejoin attempts, in seconds
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6loggerySScSgvp":{"name":"logger","abstract":"The optional function to receive logs
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC13skipHeartbeatSbvp":{"name":"skipHeartbeat","abstract":"Disables heartbeats from being sent. Default is false.
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC24disableSSLCertValidationSbvp":{"name":"disableSSLCertValidation","abstract":"Enable/Disable SSL certificate validation. Default is false. This","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC8security10Starscream17SSLTrustValidator_pSgvp":{"name":"security","abstract":"
Configure custom SSL validation logic, eg. SSL pinning. This","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC22enabledSSLCipherSuitesSays6UInt16VGSgvp":{"name":"enabledSSLCipherSuites","abstract":"
Configure the encryption used by your client by setting the","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC_6paramsACSS_SDySSypGSgtcfc":{"name":"init(_:params:)","abstract":"
Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC_13paramsClosureACSS_SDySSypGSgycSgtcfc":{"name":"init(_:paramsClosure:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17websocketProtocolSSvp":{"name":"websocketProtocol","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11isConnectedSbvp":{"name":"isConnected","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7connectyyF":{"name":"connect()","abstract":"Connects the Socket. The params passed to the Socket on initialization","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC10disconnect4code8callbacky10Starscream9CloseCodeO_yycSgtF":{"name":"disconnect(code:callback:)","abstract":"
Disconnects the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6onOpen8callbackyyyc_tF":{"name":"onOpen(callback:)","abstract":"Registers callbacks for connection open events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC14delegateOnOpen2to8callbackyx_yxctRlzClF":{"name":"delegateOnOpen(to:callback:)","abstract":"
Registers callbacks for connection open events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7onClose8callbackyyyc_tF":{"name":"onClose(callback:)","abstract":"
Registers callbacks for connection close events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC15delegateOnClose2to8callbackyx_yxctRlzClF":{"name":"delegateOnClose(to:callback:)","abstract":"
Registers callbacks for connection close events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7onError8callbackyys0F0_pc_tF":{"name":"onError(callback:)","abstract":"
Registers callbacks for connection error events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC15delegateOnError2to8callbackyx_yx_s0G0_ptctRlzClF":{"name":"delegateOnError(to:callback:)","abstract":"
Registers callbacks for connection error events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC9onMessage8callbackyyAA0F0Cc_tF":{"name":"onMessage(callback:)","abstract":"
Registers callbacks for connection message events. Does not handle","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17delegateOnMessage2to8callbackyx_yx_AA0G0CtctRlzClF":{"name":"delegateOnMessage(to:callback:)","abstract":"
Registers callbacks for connection message events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC16releaseCallbacksyyF":{"name":"releaseCallbacks()","abstract":"
Releases all stored callback hooks (onError, onOpen, onClose, etc.) You should","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7channel_6paramsAA7ChannelCSS_SDySSypGtF":{"name":"channel(_:params:)","abstract":"
Initialize a new Channel
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6removeyyAA7ChannelCF":{"name":"remove(_:)","abstract":"Removes the Channel from the socket. This does not cause the channel to","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7makeRefSSyF":{"name":"makeRef()","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC19websocketDidConnect6sockety10Starscream03WebdC0_p_tF":{"name":"websocketDidConnect(socket:)","abstract":"
Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC22websocketDidDisconnect6socket5errory10Starscream03WebdC0_p_s5Error_pSgtF":{"name":"websocketDidDisconnect(socket:error:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC26websocketDidReceiveMessage6socket4texty10Starscream03WebdC0_p_SStF":{"name":"websocketDidReceiveMessage(socket:text:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC23websocketDidReceiveData6socket4datay10Starscream03WebdC0_p_10Foundation0H0VtF":{"name":"websocketDidReceiveData(socket:data:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7channelAA7ChannelCSgvp":{"name":"channel","abstract":"The channel sending the Push
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC5eventSSvp":{"name":"event","abstract":"The event, for example phx_join
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7payloadSDySSypGvp":{"name":"payload","abstract":"The payload, for example [“user_id”: “abc123”]
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7timeoutSdvp":{"name":"timeout","abstract":"The push timeout. Default is 10.0 seconds
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC6resendyySdF":{"name":"resend(_:)","abstract":"Resets and sends the Push
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC4sendyyF":{"name":"send()","abstract":"Sends the Push. If it has already timed out, then the call will","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7receive_8callbackACSS_yAA7MessageCctF":{"name":"receive(_:callback:)","abstract":"
Receive a specific event when sending an Outbound message. Subscribing","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC15delegateReceive_2to8callbackACSS_xyx_AA7MessageCtctRlzClF":{"name":"delegateReceive(_:to:callback:)","abstract":"
Receive a specific event when sending an Outbound message. Automatically","parent_name":"Push"},"Classes/Presence/Events.html#/s:18SwiftPhoenixClient8PresenceC6EventsO5stateyA2EmF":{"name":"state","abstract":"
Undocumented
","parent_name":"Events"},"Classes/Presence/Events.html#/s:18SwiftPhoenixClient8PresenceC6EventsO4diffyA2EmF":{"name":"diff","abstract":"Undocumented
","parent_name":"Events"},"Classes/Presence/Options.html#/s:18SwiftPhoenixClient8PresenceC7OptionsV8defaultsAEvpZ":{"name":"defaults","abstract":"Default set of Options used when creating Presence. Uses the","parent_name":"Options"},"Classes/Presence/Options.html":{"name":"Options","abstract":"
Custom options that can be provided when creating Presence
","parent_name":"Presence"},"Classes/Presence/Events.html":{"name":"Events","abstract":"Presense Events
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4Metaa":{"name":"Meta","abstract":"Meta details of a Presence. Just a dictionary of properties
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC3Mapa":{"name":"Map","abstract":"A mapping of a String to an array of Metas. e.g. {“metas”: [{id: 1}]}
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC5Statea":{"name":"State","abstract":"A mapping of a Presence state to a mapping of Metas
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4Diffa":{"name":"Diff","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6OnJoina":{"name":"OnJoin","abstract":"Closure signature of OnJoin callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7OnLeavea":{"name":"OnLeave","abstract":"Closure signature for OnLeave callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6OnSynca":{"name":"OnSync","abstract":"/ Closure signature for OnSync callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC5stateSDySSSDySSSaySDySSypGGGGvp":{"name":"state","abstract":"The state of the Presence
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC12pendingDiffsSaySDySSSDySSSDySSSaySDySSypGGGGGGvp":{"name":"pendingDiffs","abstract":"Pending join and leave diffs that need to be synced
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7joinRefSSSgvp":{"name":"joinRef","abstract":"The channel’s joinRef, set when state events occur
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC18isPendingSyncStateSbvp":{"name":"isPendingSyncState","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onJoinyySS_SDySSSaySDySSypGGGSgAGtcvp":{"name":"onJoin","abstract":"Callback to be informed of joins
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onJoinyyySS_SDySSSaySDySSypGGGSgAGtcF":{"name":"onJoin(_:)","abstract":"Set the OnJoin callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7onLeaveyySS_SDySSSaySDySSypGGGAGtcvp":{"name":"onLeave","abstract":"Callback to be informed of leaves
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7onLeaveyyySS_SDySSSaySDySSypGGGAGtcF":{"name":"onLeave(_:)","abstract":"Set the OnLeave callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onSyncyycvp":{"name":"onSync","abstract":"Callback to be informed of synces
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onSyncyyyycF":{"name":"onSync(_:)","abstract":"Set the OnSync callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7channel4optsAcA7ChannelC_AC7OptionsVtcfc":{"name":"init(channel:opts:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4listSaySDySSSaySDySSypGGGGyF":{"name":"list()","abstract":"Returns the array of presences, with deault selected metadata.
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4list2bySayxGxSS_SDySSSaySDySSypGGGtXE_tlF":{"name":"list(by:)","abstract":"Returns the array of presences, with selected metadata
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6filter2bySDySSSDySSSaySDySSypGGGGSbSS_AHtcSg_tF":{"name":"filter(by:)","abstract":"Filter the Presence state with a given function
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC9syncState_03newF06onJoin0H5LeaveSDySSSDySSSaySDySSypGGGGAK_AKySS_AJSgAJtXEySS_A2JtXEtFZ":{"name":"syncState(_:newState:onJoin:onLeave:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC8syncDiff_4diff6onJoin0H5LeaveSDySSSDySSSaySDySSypGGGGAK_SDySSAKGySS_AJSgAJtXEySS_A2JtXEtFZ":{"name":"syncDiff(_:diff:onJoin:onLeave:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6filter_2bySDySSSDySSSaySDySSypGGGGAI_SbSS_AHtcSgtFZ":{"name":"filter(_:by:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6listBy_11transformerSayxGSDySSSDySSSaySDySSypGGGG_xSS_AItXEtlFZ":{"name":"listBy(_:transformer:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC3refSSvp":{"name":"ref","abstract":"Reference number. Empty if missing
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC5topicSSvp":{"name":"topic","abstract":"Message topic
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC5eventSSvp":{"name":"event","abstract":"Message event
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC7payloadSDySSypGvp":{"name":"payload","abstract":"Message payload
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC6statusSSSgvp":{"name":"status","abstract":"Convenience accessor. Equivalent to getting the status as such:
","parent_name":"Message"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC5topicSSvp":{"name":"topic","abstract":"The topic of the Channel. e.g. “rooms:friends”
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC6paramsSDySSypGvp":{"name":"params","abstract":"The params sent when joining the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9onMessageyAA0F0CAFcvp":{"name":"onMessage","abstract":"Overridable message hook. Receives all events for specialized message","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC4join7timeoutAA4PushCSdSg_tF":{"name":"join(timeout:)","abstract":"
Joins the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC7onCloseySiyAA7MessageCcF":{"name":"onClose(_:)","abstract":"Hook into when the Channel is closed. Does not handle retain cycles.","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC15delegateOnClose2to8callbackSix_yx_AA7MessageCtctRlzClF":{"name":"delegateOnClose(to:callback:)","abstract":"
Hook into when the Channel is closed. Automatically handles retain","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC7onErrorySiyAA7MessageCcF":{"name":"onError(_:)","abstract":"
Hook into when the Channel receives an Error. Does not handle retain","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC15delegateOnError2to8callbackSix_yx_AA7MessageCtctRlzClF":{"name":"delegateOnError(to:callback:)","abstract":"
Hook into when the Channel receives an Error. Automatically handles","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC2on_8callbackSiSS_yAA7MessageCctF":{"name":"on(_:callback:)","abstract":"
Subscribes on channel events. Does not handle retain cycles. Use","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC10delegateOn_2to8callbackSiSS_xyx_AA7MessageCtctRlzClF":{"name":"delegateOn(_:to:callback:)","abstract":"
Subscribes on channel events. Automatically handles retain cycles. Use","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC3off_3refySS_SiSgtF":{"name":"off(_:ref:)","abstract":"
Unsubscribes from a channel event. If a ref is given, only the exact","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC4push_7payload7timeoutAA4PushCSS_SDySSypGSdtF":{"name":"push(_:payload:timeout:)","abstract":"
Push a payload to the Channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC5leave7timeoutAA4PushCSd_tF":{"name":"leave(timeout:)","abstract":"Leaves the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9onMessage8callbackyAA0F0CAGc_tF":{"name":"onMessage(callback:)","abstract":"Overridable message hook. Receives all events for specialized message","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC8isClosedSbvp":{"name":"isClosed","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isErroredSbvp":{"name":"isErrored","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC8isJoinedSbvp":{"name":"isJoined","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isJoiningSbvp":{"name":"isJoining","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isLeavingSbvp":{"name":"isLeaving","parent_name":"Channel"},"Classes/Channel.html":{"name":"Channel","abstract":"
Undocumented
"},"Classes/Message.html":{"name":"Message","abstract":"Data that is received from the Server.
"},"Classes/Presence.html":{"name":"Presence","abstract":"The Presence object provides features for syncing presence information from"},"Classes/Push.html":{"name":"Push","abstract":"
Represnts pushing data to a Channel through the Socket
"},"Classes/Socket.html":{"name":"Socket","abstract":"Socket Connection "},"Classes/Defaults.html":{"name":"Defaults","abstract":"A collection of default values and behaviors used accross the Client
"},"Classes.html":{"name":"Classes","abstract":"The following classes are available globally.
"},"Enums.html":{"name":"Enumerations","abstract":"The following enumerations are available globally.
"},"Structs.html":{"name":"Structures","abstract":"The following structures are available globally.
"},"Typealiases.html":{"name":"Type Aliases","abstract":"The following type aliases are available globally.
"}}
================================================
FILE: docs/docsets/SwiftPhoenixClient.docset/Contents/Resources/Documents/undocumented.json
================================================
{
"warnings": [
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 118,
"symbol": "Presence.Events.state",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 119,
"symbol": "Presence.Events.diff",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 136,
"symbol": "Presence.Diff",
"symbol_kind": "source.lang.swift.decl.typealias",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 173,
"symbol": "Presence.isPendingSyncState",
"symbol_kind": "source.lang.swift.decl.var.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 212,
"symbol": "Presence.init(channel:opts:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 285,
"symbol": "Presence.syncState(_:newState:onJoin:onLeave:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 340,
"symbol": "Presence.syncDiff(_:diff:onJoin:onLeave:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 380,
"symbol": "Presence.filter(_:by:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Presence.swift",
"line": 386,
"symbol": "Presence.listBy(_:transformer:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Socket.swift",
"line": 143,
"symbol": "Socket.init(_:params:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Socket.swift",
"line": 663,
"symbol": "Socket.websocketDidConnect(socket:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Socket.swift",
"line": 667,
"symbol": "Socket.websocketDidDisconnect(socket:error:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Socket.swift",
"line": 672,
"symbol": "Socket.websocketDidReceiveMessage(socket:text:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Socket.swift",
"line": 676,
"symbol": "Socket.websocketDidReceiveData(socket:data:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 59,
"symbol": "ChannelState.closed",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 60,
"symbol": "ChannelState.errored",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 61,
"symbol": "ChannelState.joined",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 62,
"symbol": "ChannelState.joining",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 63,
"symbol": "ChannelState.leaving",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 69,
"symbol": "ChannelEvent.heartbeat",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 70,
"symbol": "ChannelEvent.join",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 71,
"symbol": "ChannelEvent.leave",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 72,
"symbol": "ChannelEvent.reply",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 73,
"symbol": "ChannelEvent.error",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Defaults.swift",
"line": 74,
"symbol": "ChannelEvent.close",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 31,
"symbol": "Delegated.init()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 33,
"symbol": "Delegated.delegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 43,
"symbol": "Delegated.call(_:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 47,
"symbol": "Delegated.isDelegateSet",
"symbol_kind": "source.lang.swift.decl.var.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 55,
"symbol": "Delegated.stronglyDelegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 62,
"symbol": "Delegated.manuallyDelegate(with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 66,
"symbol": "Delegated.removeDelegate()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 74,
"symbol": "Delegated.delegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 79,
"symbol": "Delegated.stronglyDelegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 88,
"symbol": "Delegated.call()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 96,
"symbol": "Delegated.call(_:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/client/Utilities/Delegated.swift",
"line": 104,
"symbol": "Delegated.call()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
}
],
"source_directory": "/Users/drees/src/github/phoenix/SwiftPhoenixClient"
}
================================================
FILE: docs/index.html
================================================
SwiftPhoenixClient Reference
SwiftPhoenixClient Reference
SwiftPhoenixClient Reference
Swift Phoenix Client
About
Swift Phoenix Client is an extension of Starscream websocket client library
that makes it easy to connect to Phoenix sockets in a similar manner to the
phoenix.js client.
The client is currently updated to mirror phoenix.js 1.4.
Swift Versions
master currently supports Swift 5.0. You’ll need to set your target to = 1.0.1 if your project is using Swift 4.2
swift
client
4.2
1.0.1
5.0
1.1.0
Installation
CocoaPods
You can install SwiftPhoenix Client via CocoaPods by adding the following to your
Podfile. Keep in mind that in order to use Swift Phoenix Client, the minimum iOS
target must be ‘9.0’
platform :ios, '9.0'
use_frameworks!
pod "SwiftPhoenixClient", '~> 1.0'
and running pod install. From there you will need to add import SwiftPhoenixClient in any class you want it to be used.
Carthage
If you use Carthage to manage your dependencies, simply add
SwiftPhoenixClient to your Cartfile:
github "davidstump/SwiftPhoenixClient" ~> 1.0
SwiftPackageManager
SwiftPackageManager is properly supported starting in SwiftPhoenixClient v1.2.0. You can add the following to your Package.swift
.package(url: "https://github.com/davidstump/SwiftPhoenixClient.git", .upToNextMajor(from: "1.2.0"))
Make sure you have added SwiftPhoenixClient.framework, and Starscream.framework to the “Linked Frameworks and Libraries ” section of your target, and have included them in your Carthage framework copying build phase.
Usage
Using the Swift Phoenix Client is extremely easy (and familiar if have used the phoenix.s client).
See the Usage Guide for details instructions. You can also check out the documentation
Example
Check out the ViewController in this repo for a brief example of a simple iOS chat application using the Phoenix Chat Example
Also check out both the Swift and Elixir channels on IRC.
Development
Check out the wiki page for getting started
Tested with the Phoenix Chat Server example , upgraded to Phoenix 1.2.
Thanks
Many many thanks to Daniel Rees for his many contributions and continued maintenance of this project!
License
SwiftPhoenixClient is available under the MIT license. See the LICENSE file for more info.
================================================
FILE: docs/js/jazzy.js
================================================
window.jazzy = {'docset': false}
if (typeof window.dash != 'undefined') {
document.documentElement.className += ' dash'
window.jazzy.docset = true
}
if (navigator.userAgent.match(/xcode/i)) {
document.documentElement.className += ' xcode'
window.jazzy.docset = true
}
function toggleItem($link, $content) {
var animationDuration = 300;
$link.toggleClass('token-open');
$content.slideToggle(animationDuration);
}
function itemLinkToContent($link) {
return $link.parent().parent().next();
}
// On doc load + hash-change, open any targetted item
function openCurrentItemIfClosed() {
if (window.jazzy.docset) {
return;
}
var $link = $(`.token[href="${location.hash}"]`);
$content = itemLinkToContent($link);
if ($content.is(':hidden')) {
toggleItem($link, $content);
}
}
$(openCurrentItemIfClosed);
$(window).on('hashchange', openCurrentItemIfClosed);
// On item link ('token') click, toggle its discussion
$('.token').on('click', function(event) {
if (window.jazzy.docset) {
return;
}
var $link = $(this);
toggleItem($link, itemLinkToContent($link));
// Keeps the document from jumping to the hash.
var href = $link.attr('href');
if (history.pushState) {
history.pushState({}, '', href);
} else {
location.hash = href;
}
event.preventDefault();
});
// Clicks on links to the current, closed, item need to open the item
$("a:not('.token')").on('click', function() {
if (location == this.href) {
openCurrentItemIfClosed();
}
});
================================================
FILE: docs/search.json
================================================
{"Typealiases.html#/s:18SwiftPhoenixClient7Payloada":{"name":"Payload","abstract":"Alias for a JSON dictionary [String: Any]
"},"Typealiases.html#/s:18SwiftPhoenixClient14PayloadClosurea":{"name":"PayloadClosure","abstract":"Alias for a function returning an optional JSON dictionary (Payload?)
"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVACyxq_Gycfc":{"name":"init()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV8delegate2to4withyqd___q_qd___xtctRld__ClF":{"name":"delegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV4callyq_SgxF":{"name":"call(_:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV13isDelegateSetSbvp":{"name":"isDelegateSet","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV16stronglyDelegate2to4withyqd___q_qd___xtctRld__ClF":{"name":"stronglyDelegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV16manuallyDelegate4withyq_xc_tF":{"name":"manuallyDelegate(with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedV14removeDelegateyyF":{"name":"removeDelegate()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE8delegate2to4withyqd___q_qd__ctRld__ClF":{"name":"delegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE16stronglyDelegate2to4withyqd___q_qd__ctRld__ClF":{"name":"stronglyDelegate(to:with:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszrlE4callq_SgyF":{"name":"call()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRs_rlE4callyyxF":{"name":"call(_:)","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/Delegated.html#/s:18SwiftPhoenixClient9DelegatedVAAytRszytRs_rlE4callyyF":{"name":"call()","abstract":"Undocumented
","parent_name":"Delegated"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV9heartbeatSSvpZ":{"name":"heartbeat","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV4joinSSvpZ":{"name":"join","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5leaveSSvpZ":{"name":"leave","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5replySSvpZ":{"name":"reply","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5errorSSvpZ":{"name":"error","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html#/s:18SwiftPhoenixClient12ChannelEventV5closeSSvpZ":{"name":"close","abstract":"Undocumented
","parent_name":"ChannelEvent"},"Structs/ChannelEvent.html":{"name":"ChannelEvent","abstract":"Represents the different events that can be sent through"},"Structs/Delegated.html":{"name":"Delegated","abstract":"
Provides a memory-safe way of passing callbacks around while not creating"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO6closedyA2CmF":{"name":"closed","abstract":"
Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7erroredyA2CmF":{"name":"errored","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO6joinedyA2CmF":{"name":"joined","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7joiningyA2CmF":{"name":"joining","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html#/s:18SwiftPhoenixClient12ChannelStateO7leavingyA2CmF":{"name":"leaving","abstract":"Undocumented
","parent_name":"ChannelState"},"Enums/ChannelState.html":{"name":"ChannelState","abstract":"Represents the multiple states that a Channel can be in"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC15timeoutIntervalSdvpZ":{"name":"timeoutInterval","abstract":"
Default timeout when sending messages
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC17heartbeatIntervalSdvpZ":{"name":"heartbeatInterval","abstract":"Default interval to send heartbeats on
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC23reconnectSteppedBackOffySdSicvpZ":{"name":"reconnectSteppedBackOff","abstract":"Default reconnect algorithm for the socket
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC20rejoinSteppedBackOffySdSicvpZ":{"name":"rejoinSteppedBackOff","abstract":"Default rejoin algorithm for individual channels
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC6encodey10Foundation4DataVSDySSypGcvpZ":{"name":"encode","abstract":"Default encode function, utilizing JSONSerialization.data
","parent_name":"Defaults"},"Classes/Defaults.html#/s:18SwiftPhoenixClient8DefaultsC6decodeySDySSypGSg10Foundation4DataVcvpZ":{"name":"decode","abstract":"Default decode function, utilizing JSONSerialization.jsonObject
","parent_name":"Defaults"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC8endPointSSvp":{"name":"endPoint","abstract":"The string WebSocket endpoint (ie "ws://example.com/socket",","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11endPointUrl10Foundation3URLVvp":{"name":"endPointUrl","abstract":"
The fully qualified socket URL
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6paramsSDySSypGSgvp":{"name":"params","abstract":"Resolves to return the paramsClosure result at the time of calling.","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC13paramsClosureSDySSypGSgycSgvp":{"name":"paramsClosure","abstract":"
The optional params closure used to get params whhen connecting. Must","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6encodey10Foundation4DataVSDySSypGcvp":{"name":"encode","abstract":"
Override to provide custom encoding of data before writing to the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6decodeySDySSypGSg10Foundation4DataVcvp":{"name":"decode","abstract":"Override to provide customd decoding of data read from the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7timeoutSdvp":{"name":"timeout","abstract":"Timeout to use when opening connections
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17heartbeatIntervalSdvp":{"name":"heartbeatInterval","abstract":"Interval between sending a heartbeat
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC14reconnectAfterySdSicvp":{"name":"reconnectAfter","abstract":"Interval between socket reconnect attempts, in seconds
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11rejoinAfterySdSicvp":{"name":"rejoinAfter","abstract":"Interval between channel rejoin attempts, in seconds
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6loggerySScSgvp":{"name":"logger","abstract":"The optional function to receive logs
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC13skipHeartbeatSbvp":{"name":"skipHeartbeat","abstract":"Disables heartbeats from being sent. Default is false.
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC24disableSSLCertValidationSbvp":{"name":"disableSSLCertValidation","abstract":"Enable/Disable SSL certificate validation. Default is false. This","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC8security10Starscream17SSLTrustValidator_pSgvp":{"name":"security","abstract":"
Configure custom SSL validation logic, eg. SSL pinning. This","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC22enabledSSLCipherSuitesSays6UInt16VGSgvp":{"name":"enabledSSLCipherSuites","abstract":"
Configure the encryption used by your client by setting the","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC_6paramsACSS_SDySSypGSgtcfc":{"name":"init(_:params:)","abstract":"
Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC_13paramsClosureACSS_SDySSypGSgycSgtcfc":{"name":"init(_:paramsClosure:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17websocketProtocolSSvp":{"name":"websocketProtocol","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC11isConnectedSbvp":{"name":"isConnected","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7connectyyF":{"name":"connect()","abstract":"Connects the Socket. The params passed to the Socket on initialization","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC10disconnect4code8callbacky10Starscream9CloseCodeO_yycSgtF":{"name":"disconnect(code:callback:)","abstract":"
Disconnects the socket
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6onOpen8callbackyyyc_tF":{"name":"onOpen(callback:)","abstract":"Registers callbacks for connection open events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC14delegateOnOpen2to8callbackyx_yxctRlzClF":{"name":"delegateOnOpen(to:callback:)","abstract":"
Registers callbacks for connection open events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7onClose8callbackyyyc_tF":{"name":"onClose(callback:)","abstract":"
Registers callbacks for connection close events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC15delegateOnClose2to8callbackyx_yxctRlzClF":{"name":"delegateOnClose(to:callback:)","abstract":"
Registers callbacks for connection close events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7onError8callbackyys0F0_pc_tF":{"name":"onError(callback:)","abstract":"
Registers callbacks for connection error events. Does not handle retain","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC15delegateOnError2to8callbackyx_yx_s0G0_ptctRlzClF":{"name":"delegateOnError(to:callback:)","abstract":"
Registers callbacks for connection error events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC9onMessage8callbackyyAA0F0Cc_tF":{"name":"onMessage(callback:)","abstract":"
Registers callbacks for connection message events. Does not handle","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC17delegateOnMessage2to8callbackyx_yx_AA0G0CtctRlzClF":{"name":"delegateOnMessage(to:callback:)","abstract":"
Registers callbacks for connection message events. Automatically handles","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC16releaseCallbacksyyF":{"name":"releaseCallbacks()","abstract":"
Releases all stored callback hooks (onError, onOpen, onClose, etc.) You should","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7channel_6paramsAA7ChannelCSS_SDySSypGtF":{"name":"channel(_:params:)","abstract":"
Initialize a new Channel
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC6removeyyAA7ChannelCF":{"name":"remove(_:)","abstract":"Removes the Channel from the socket. This does not cause the channel to","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC7makeRefSSyF":{"name":"makeRef()","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC19websocketDidConnect6sockety10Starscream03WebdC0_p_tF":{"name":"websocketDidConnect(socket:)","abstract":"
Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC22websocketDidDisconnect6socket5errory10Starscream03WebdC0_p_s5Error_pSgtF":{"name":"websocketDidDisconnect(socket:error:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC26websocketDidReceiveMessage6socket4texty10Starscream03WebdC0_p_SStF":{"name":"websocketDidReceiveMessage(socket:text:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Socket.html#/s:18SwiftPhoenixClient6SocketC23websocketDidReceiveData6socket4datay10Starscream03WebdC0_p_10Foundation0H0VtF":{"name":"websocketDidReceiveData(socket:data:)","abstract":"Undocumented
","parent_name":"Socket"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7channelAA7ChannelCSgvp":{"name":"channel","abstract":"The channel sending the Push
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC5eventSSvp":{"name":"event","abstract":"The event, for example phx_join
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7payloadSDySSypGvp":{"name":"payload","abstract":"The payload, for example [“user_id”: “abc123”]
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7timeoutSdvp":{"name":"timeout","abstract":"The push timeout. Default is 10.0 seconds
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC6resendyySdF":{"name":"resend(_:)","abstract":"Resets and sends the Push
","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC4sendyyF":{"name":"send()","abstract":"Sends the Push. If it has already timed out, then the call will","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC7receive_8callbackACSS_yAA7MessageCctF":{"name":"receive(_:callback:)","abstract":"
Receive a specific event when sending an Outbound message. Subscribing","parent_name":"Push"},"Classes/Push.html#/s:18SwiftPhoenixClient4PushC15delegateReceive_2to8callbackACSS_xyx_AA7MessageCtctRlzClF":{"name":"delegateReceive(_:to:callback:)","abstract":"
Receive a specific event when sending an Outbound message. Automatically","parent_name":"Push"},"Classes/Presence/Events.html#/s:18SwiftPhoenixClient8PresenceC6EventsO5stateyA2EmF":{"name":"state","abstract":"
Undocumented
","parent_name":"Events"},"Classes/Presence/Events.html#/s:18SwiftPhoenixClient8PresenceC6EventsO4diffyA2EmF":{"name":"diff","abstract":"Undocumented
","parent_name":"Events"},"Classes/Presence/Options.html#/s:18SwiftPhoenixClient8PresenceC7OptionsV8defaultsAEvpZ":{"name":"defaults","abstract":"Default set of Options used when creating Presence. Uses the","parent_name":"Options"},"Classes/Presence/Options.html":{"name":"Options","abstract":"
Custom options that can be provided when creating Presence
","parent_name":"Presence"},"Classes/Presence/Events.html":{"name":"Events","abstract":"Presense Events
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4Metaa":{"name":"Meta","abstract":"Meta details of a Presence. Just a dictionary of properties
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC3Mapa":{"name":"Map","abstract":"A mapping of a String to an array of Metas. e.g. {“metas”: [{id: 1}]}
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC5Statea":{"name":"State","abstract":"A mapping of a Presence state to a mapping of Metas
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4Diffa":{"name":"Diff","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6OnJoina":{"name":"OnJoin","abstract":"Closure signature of OnJoin callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7OnLeavea":{"name":"OnLeave","abstract":"Closure signature for OnLeave callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6OnSynca":{"name":"OnSync","abstract":"/ Closure signature for OnSync callbacks
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC5stateSDySSSDySSSaySDySSypGGGGvp":{"name":"state","abstract":"The state of the Presence
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC12pendingDiffsSaySDySSSDySSSDySSSaySDySSypGGGGGGvp":{"name":"pendingDiffs","abstract":"Pending join and leave diffs that need to be synced
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7joinRefSSSgvp":{"name":"joinRef","abstract":"The channel’s joinRef, set when state events occur
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC18isPendingSyncStateSbvp":{"name":"isPendingSyncState","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onJoinyySS_SDySSSaySDySSypGGGSgAGtcvp":{"name":"onJoin","abstract":"Callback to be informed of joins
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onJoinyyySS_SDySSSaySDySSypGGGSgAGtcF":{"name":"onJoin(_:)","abstract":"Set the OnJoin callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7onLeaveyySS_SDySSSaySDySSypGGGAGtcvp":{"name":"onLeave","abstract":"Callback to be informed of leaves
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7onLeaveyyySS_SDySSSaySDySSypGGGAGtcF":{"name":"onLeave(_:)","abstract":"Set the OnLeave callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onSyncyycvp":{"name":"onSync","abstract":"Callback to be informed of synces
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6onSyncyyyycF":{"name":"onSync(_:)","abstract":"Set the OnSync callback
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC7channel4optsAcA7ChannelC_AC7OptionsVtcfc":{"name":"init(channel:opts:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4listSaySDySSSaySDySSypGGGGyF":{"name":"list()","abstract":"Returns the array of presences, with deault selected metadata.
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC4list2bySayxGxSS_SDySSSaySDySSypGGGtXE_tlF":{"name":"list(by:)","abstract":"Returns the array of presences, with selected metadata
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6filter2bySDySSSDySSSaySDySSypGGGGSbSS_AHtcSg_tF":{"name":"filter(by:)","abstract":"Filter the Presence state with a given function
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC9syncState_03newF06onJoin0H5LeaveSDySSSDySSSaySDySSypGGGGAK_AKySS_AJSgAJtXEySS_A2JtXEtFZ":{"name":"syncState(_:newState:onJoin:onLeave:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC8syncDiff_4diff6onJoin0H5LeaveSDySSSDySSSaySDySSypGGGGAK_SDySSAKGySS_AJSgAJtXEySS_A2JtXEtFZ":{"name":"syncDiff(_:diff:onJoin:onLeave:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6filter_2bySDySSSDySSSaySDySSypGGGGAI_SbSS_AHtcSgtFZ":{"name":"filter(_:by:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Presence.html#/s:18SwiftPhoenixClient8PresenceC6listBy_11transformerSayxGSDySSSDySSSaySDySSypGGGG_xSS_AItXEtlFZ":{"name":"listBy(_:transformer:)","abstract":"Undocumented
","parent_name":"Presence"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC3refSSvp":{"name":"ref","abstract":"Reference number. Empty if missing
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC5topicSSvp":{"name":"topic","abstract":"Message topic
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC5eventSSvp":{"name":"event","abstract":"Message event
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC7payloadSDySSypGvp":{"name":"payload","abstract":"Message payload
","parent_name":"Message"},"Classes/Message.html#/s:18SwiftPhoenixClient7MessageC6statusSSSgvp":{"name":"status","abstract":"Convenience accessor. Equivalent to getting the status as such:
","parent_name":"Message"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC5topicSSvp":{"name":"topic","abstract":"The topic of the Channel. e.g. “rooms:friends”
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC6paramsSDySSypGvp":{"name":"params","abstract":"The params sent when joining the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9onMessageyAA0F0CAFcvp":{"name":"onMessage","abstract":"Overridable message hook. Receives all events for specialized message","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC4join7timeoutAA4PushCSdSg_tF":{"name":"join(timeout:)","abstract":"
Joins the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC7onCloseySiyAA7MessageCcF":{"name":"onClose(_:)","abstract":"Hook into when the Channel is closed. Does not handle retain cycles.","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC15delegateOnClose2to8callbackSix_yx_AA7MessageCtctRlzClF":{"name":"delegateOnClose(to:callback:)","abstract":"
Hook into when the Channel is closed. Automatically handles retain","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC7onErrorySiyAA7MessageCcF":{"name":"onError(_:)","abstract":"
Hook into when the Channel receives an Error. Does not handle retain","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC15delegateOnError2to8callbackSix_yx_AA7MessageCtctRlzClF":{"name":"delegateOnError(to:callback:)","abstract":"
Hook into when the Channel receives an Error. Automatically handles","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC2on_8callbackSiSS_yAA7MessageCctF":{"name":"on(_:callback:)","abstract":"
Subscribes on channel events. Does not handle retain cycles. Use","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC10delegateOn_2to8callbackSiSS_xyx_AA7MessageCtctRlzClF":{"name":"delegateOn(_:to:callback:)","abstract":"
Subscribes on channel events. Automatically handles retain cycles. Use","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC3off_3refySS_SiSgtF":{"name":"off(_:ref:)","abstract":"
Unsubscribes from a channel event. If a ref is given, only the exact","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC4push_7payload7timeoutAA4PushCSS_SDySSypGSdtF":{"name":"push(_:payload:timeout:)","abstract":"
Push a payload to the Channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC5leave7timeoutAA4PushCSd_tF":{"name":"leave(timeout:)","abstract":"Leaves the channel
","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9onMessage8callbackyAA0F0CAGc_tF":{"name":"onMessage(callback:)","abstract":"Overridable message hook. Receives all events for specialized message","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC8isClosedSbvp":{"name":"isClosed","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isErroredSbvp":{"name":"isErrored","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC8isJoinedSbvp":{"name":"isJoined","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isJoiningSbvp":{"name":"isJoining","parent_name":"Channel"},"Classes/Channel.html#/s:18SwiftPhoenixClient7ChannelC9isLeavingSbvp":{"name":"isLeaving","parent_name":"Channel"},"Classes/Channel.html":{"name":"Channel","abstract":"
Undocumented
"},"Classes/Message.html":{"name":"Message","abstract":"Data that is received from the Server.
"},"Classes/Presence.html":{"name":"Presence","abstract":"The Presence object provides features for syncing presence information from"},"Classes/Push.html":{"name":"Push","abstract":"
Represnts pushing data to a Channel through the Socket
"},"Classes/Socket.html":{"name":"Socket","abstract":"Socket Connection "},"Classes/Defaults.html":{"name":"Defaults","abstract":"A collection of default values and behaviors used accross the Client
"},"Classes.html":{"name":"Classes","abstract":"The following classes are available globally.
"},"Enums.html":{"name":"Enumerations","abstract":"The following enumerations are available globally.
"},"Structs.html":{"name":"Structures","abstract":"The following structures are available globally.
"},"Typealiases.html":{"name":"Type Aliases","abstract":"The following type aliases are available globally.
"}}
================================================
FILE: docs/undocumented.json
================================================
{
"warnings": [
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Channel.swift",
"line": 61,
"symbol": "Channel",
"symbol_kind": "source.lang.swift.decl.class",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Channel.swift",
"line": 601,
"symbol": "Channel",
"symbol_kind": "source.lang.swift.decl.extension",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 118,
"symbol": "Presence.Events.state",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 119,
"symbol": "Presence.Events.diff",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 136,
"symbol": "Presence.Diff",
"symbol_kind": "source.lang.swift.decl.typealias",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 173,
"symbol": "Presence.isPendingSyncState",
"symbol_kind": "source.lang.swift.decl.var.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 212,
"symbol": "Presence.init(channel:opts:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 285,
"symbol": "Presence.syncState(_:newState:onJoin:onLeave:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 340,
"symbol": "Presence.syncDiff(_:diff:onJoin:onLeave:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 380,
"symbol": "Presence.filter(_:by:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Presence.swift",
"line": 386,
"symbol": "Presence.listBy(_:transformer:)",
"symbol_kind": "source.lang.swift.decl.function.method.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 161,
"symbol": "Socket.init(_:params:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 168,
"symbol": "Socket.init(_:paramsClosure:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 723,
"symbol": "Socket.websocketDidConnect(socket:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 727,
"symbol": "Socket.websocketDidDisconnect(socket:error:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 732,
"symbol": "Socket.websocketDidReceiveMessage(socket:text:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Socket.swift",
"line": 736,
"symbol": "Socket.websocketDidReceiveData(socket:data:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 66,
"symbol": "ChannelState.closed",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 67,
"symbol": "ChannelState.errored",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 68,
"symbol": "ChannelState.joined",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 69,
"symbol": "ChannelState.joining",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 70,
"symbol": "ChannelState.leaving",
"symbol_kind": "source.lang.swift.decl.enumelement",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 76,
"symbol": "ChannelEvent.heartbeat",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 77,
"symbol": "ChannelEvent.join",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 78,
"symbol": "ChannelEvent.leave",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 79,
"symbol": "ChannelEvent.reply",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 80,
"symbol": "ChannelEvent.error",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Defaults.swift",
"line": 81,
"symbol": "ChannelEvent.close",
"symbol_kind": "source.lang.swift.decl.var.static",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 31,
"symbol": "Delegated.init()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 33,
"symbol": "Delegated.delegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 43,
"symbol": "Delegated.call(_:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 47,
"symbol": "Delegated.isDelegateSet",
"symbol_kind": "source.lang.swift.decl.var.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 55,
"symbol": "Delegated.stronglyDelegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 62,
"symbol": "Delegated.manuallyDelegate(with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 66,
"symbol": "Delegated.removeDelegate()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 74,
"symbol": "Delegated.delegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 79,
"symbol": "Delegated.stronglyDelegate(to:with:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 88,
"symbol": "Delegated.call()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 96,
"symbol": "Delegated.call(_:)",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
},
{
"file": "/Users/drees/src/github/phoenix/SwiftPhoenixClient/Sources/Utilities/Delegated.swift",
"line": 104,
"symbol": "Delegated.call()",
"symbol_kind": "source.lang.swift.decl.function.method.instance",
"warning": "undocumented"
}
],
"source_directory": "/Users/drees/src/github/phoenix/SwiftPhoenixClient"
}
================================================
FILE: fastlane/Appfile
================================================
# app_identifier "[[APP_IDENTIFIER]]" # The bundle identifier of your app
# apple_id "[[APPLE_ID]]" # Your Apple email address
# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
================================================
FILE: fastlane/Fastfile
================================================
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:ios)
platform :ios do
desc "Runs tests for the project"
lane :test do
execute_tests
gather_coverage
end
# PRIVATE LANES
private_lane :execute_tests do
scan(
scheme: "SwiftPhoenixClient",
code_coverage: true
)
end
private_lane :gather_coverage do
slather(
use_bundle_exec: true,
cobertura_xml: true,
travis: true
)
end
end
================================================
FILE: fastlane/README.md
================================================
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
### ios test
```
fastlane ios test
```
Runs tests for the project
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
================================================
FILE: sourcery/MockableClass.stencil
================================================
// swiftlint:disable line_length
// swiftlint:disable variable_name
@testable import SwiftPhoenixClient
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro methodThrowableErrorDeclaration method %}
var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% endmacro %}
{% macro argumentsBlock arguments %}{% filter removeNewlines:"leading" %}
{% for argument in arguments %}
{{argument.name}}{% if not forloop.last %}, {% endif %}
{% endfor %}
{% endfilter %}{% endmacro %}
{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
throw error
}
{% endmacro %}
{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{{ method.returnTypeName }}{% endif %})?
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro mockMethod method %}
//MARK: - {{ method.shortName }}
{% if method.throws %}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 and method.isGeneric == false %}
var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {% if param.isClosure %}({% endif %}{{ param.typeName.unwrappedTypeName }}{% if param.isClosure %}){% endif %}?{% endfor %}
{% else %}{% if not method.parameters.count == 0 and method.isGeneric == false %}
var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{{ param.unwrappedTypeName }}{% else %}{{ param.typeName }}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %})?
{% endif %}{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ method.returnTypeName }}!
{% endif %}
{% if method.isGeneric == false %}
{% call methodClosureDeclaration method %}
{% endif %}
{% if method.isInitializer or method.isDeinitializer %}
{#{% if not method.isConvenienceInitializer %}
override {{ method.name }} {
super.{{ method.callName }}({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% ifnot forloop.last %}, {% endif %}{% endfor %})
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% endif %}#}
{% else %}
override func {{ method.name }}{% if method.throws %} throws{% endif %}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% if method.isGeneric == false %}
{% call methodReceivedParameters method %}
{% endif %}
{% if method.returnTypeName.isVoid %}
{% if method.isGeneric == false %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% endif %}
{% else %}
{% if method.isGeneric == true %}
return {% call swiftifyMethodName method.selectorName %}ReturnValue
{% else %}
return {% if method.throws %}try {% endif %}{% call methodClosureName method %}.map({ {% if method.throws %}try {% endif %}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue
{% endif %}
{% endif %}
}
{% endif %}
{% endmacro %}
{% macro mockOptionalVariable variable %}
{% if variable.writeAccess == "" %}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}!
override var {% call mockedVariableName variable %}: {{ variable.typeName }} {
return {% call underlyingMockedVariableName variable %}
}
{% else %}
var {{ variable.name }}SetCount: Int = 0
var {{ variable.name }}DidGetSet: Bool { return {{ variable.name }}SetCount > 0 }
override var {% call mockedVariableName variable %}: {{ variable.typeName }} {
didSet { {{ variable.name }}SetCount += 1 }
}
{% endif %}
{% endmacro %}
{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
{# var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %} #}
{% endmacro %}
{% macro mockNonOptionalVariable variable %}
override var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get { return {% call underlyingMockedVariableName variable %} }
set(value) { {% call underlyingMockedVariableName variable %} = value }
}
var {% call underlyingMockedVariableName variable %}: ({{ variable.typeName }})!
{% endmacro %}
{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}
{% for type in types.classes where type.name == "TimeoutTimer" or type.name == "Socket" or type.name == "Push" or type.name == "Channel" %}
class {{ type.name }}Mock: {{ type.name }} {
{# Generate all variable mocks #}
{% for variable in type.allVariables|!definedInExtension where variable.readAccess != "private" and variable.isMutable %}
{% if variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %}
{% endfor %}
{% for method in type.allMethods|!definedInExtension where method.accessLevel != "private" and method.actualDefinedInTypeName.name != "WebSocketDelegate" %}
{% call mockMethod method %}
{% endfor %}
}
{% endfor %}
================================================
FILE: sourcery/MockableProtocol.stencil
================================================
// swiftlint:disable line_length
// swiftlint:disable variable_name
import Foundation
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#elseif os(OSX)
import AppKit
#endif
@testable import SwiftPhoenixClient
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro methodThrowableErrorDeclaration method %}
var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% endmacro %}
{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
throw error
}
{% endmacro %}
{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append({% for param in method.parameters %}{{ param.name }}){% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% call swiftifyMethodName method.selectorName %}ReceivedInvocations.append(({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %}))
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro closureReturnTypeName method %}{% if method.isOptionalReturnType %}{{ method.unwrappedReturnTypeName }}?{% else %}{{ method.returnTypeName }}{% endif %}{% endmacro %}
{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{% call closureReturnTypeName method %}{% endif %})?
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro mockMethod method %}
//MARK: - {{ method.shortName }}
{% if method.throws %}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 %}
var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}?{% endfor %}
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations{% for param in method.parameters %}: [{{ '(' if param.isClosure }}{{ param.typeName.unwrappedTypeName }}{{ ')' if param.isClosure }}{%if param.typeName.isOptional%}?{%endif%}]{% endfor %} = []
{% elif not method.parameters.count == 0 %}
var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})?
var {% call swiftifyMethodName method.selectorName %}ReceivedInvocations: [({% for param in method.parameters %}{{ param.name }}: {{ param.unwrappedTypeName if param.typeAttributes.escaping else param.typeName }}{{ ', ' if not forloop.last }}{% endfor %})] = []
{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ '(' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ method.returnTypeName }}{{ ')' if method.returnTypeName.isClosure and not method.isOptionalReturnType }}{{ '!' if not method.isOptionalReturnType }}
{% endif %}
{% call methodClosureDeclaration method %}
{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% else %}
func {{ method.name }}{{ ' throws' if method.throws }}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% else %}
return {{ 'try ' if method.throws }}{% call methodClosureName method %}.map({ {{ 'try ' if method.throws }}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue
{% endif %}
}
{% endif %}
{% endmacro %}
{% macro mockOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }}
{% endmacro %}
{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %}
{% endmacro %}
{% macro mockNonOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get { return {% call underlyingMockedVariableName variable %} }
set(value) { {% call underlyingMockedVariableName variable %} = value }
}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}!
{% endmacro %}
{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}
{% for type in types.protocols where type.based.AutoMockable or type|annotated:"AutoMockable" %}{% if type.name != "AutoMockable" %}
class {{ type.name }}Mock: {{ type.name }} {
{% for variable in type.allVariables|!definedInExtension %}
{% if variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %}
{% endfor %}
{% for method in type.allMethods|!definedInExtension %}
{% call mockMethod method %}
{% endfor %}
}
{% endif %}{% endfor %}
================================================
FILE: sourcery/MockableWebSocketClient.stencil
================================================
// swiftlint:disable line_length
// swiftlint:disable variable_name
import Starscream
@testable import SwiftPhoenixClient
{% macro swiftifyMethodName name %}{{ name | replace:"(","_" | replace:")","" | replace:":","_" | replace:"`","" | snakeToCamelCase | lowerFirstWord }}{% endmacro %}
{% macro methodThrowableErrorDeclaration method %}
var {% call swiftifyMethodName method.selectorName %}ThrowableError: Error?
{% endmacro %}
{% macro methodThrowableErrorUsage method %}
if let error = {% call swiftifyMethodName method.selectorName %}ThrowableError {
throw error
}
{% endmacro %}
{% macro methodReceivedParameters method %}
{%if method.parameters.count == 1 %}
{% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }} = {{ param.name }}{% endfor %}
{% else %}
{% if not method.parameters.count == 0 %}
{% call swiftifyMethodName method.selectorName %}ReceivedArguments = ({% for param in method.parameters %}{{ param.name }}: {{ param.name }}{% if not forloop.last%}, {% endif %}{% endfor %})
{% endif %}
{% endif %}
{% endmacro %}
{% macro methodClosureName method %}{% call swiftifyMethodName method.selectorName %}Closure{% endmacro %}
{% macro methodClosureDeclaration method %}
var {% call methodClosureName method %}: (({% for param in method.parameters %}{{ param.typeName }}{% if not forloop.last %}, {% endif %}{% endfor %}) {% if method.throws %}throws {% endif %}-> {% if method.isInitializer %}Void{% else %}{{ method.returnTypeName }}{% endif %})?
{% endmacro %}
{% macro methodClosureCallParameters method %}{% for param in method.parameters %}{{ param.name }}{% if not forloop.last %}, {% endif %}{% endfor %}{% endmacro %}
{% macro mockMethod method %}
//MARK: - {{ method.shortName }}
{% if method.throws %}
{% call methodThrowableErrorDeclaration method %}
{% endif %}
{% if not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}CallsCount = 0
var {% call swiftifyMethodName method.selectorName %}Called: Bool {
return {% call swiftifyMethodName method.selectorName %}CallsCount > 0
}
{% endif %}
{% if method.parameters.count == 1 %}
var {% call swiftifyMethodName method.selectorName %}Received{% for param in method.parameters %}{{ param.name|upperFirstLetter }}: {% if param.isClosure %}({% endif %}{{ param.typeName.unwrappedTypeName }}{% if param.isClosure %}){% endif %}?{% endfor %}
{% else %}{% if not method.parameters.count == 0 %}
var {% call swiftifyMethodName method.selectorName %}ReceivedArguments: ({% for param in method.parameters %}{{ param.name }}: {% if param.typeAttributes.escaping %}{{ param.unwrappedTypeName }}{% else %}{{ param.typeName }}{% endif %}{% if not forloop.last %}, {% endif %}{% endfor %})?
{% endif %}{% endif %}
{% if not method.returnTypeName.isVoid and not method.isInitializer %}
var {% call swiftifyMethodName method.selectorName %}ReturnValue: {{ method.returnTypeName }}!
{% endif %}
{% call methodClosureDeclaration method %}
{% if method.isInitializer %}
required {{ method.name }} {
{% call methodReceivedParameters method %}
{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
}
{% else %}
func {{ method.name }}{% if method.throws %} throws{% endif %}{% if not method.returnTypeName.isVoid %} -> {{ method.returnTypeName }}{% endif %} {
{% if method.throws %}
{% call methodThrowableErrorUsage method %}
{% endif %}
{% call swiftifyMethodName method.selectorName %}CallsCount += 1
{% call methodReceivedParameters method %}
{% if method.returnTypeName.isVoid %}
{% if method.throws %}try {% endif %}{% call methodClosureName method %}?({% call methodClosureCallParameters method %})
{% else %}
return {% if method.throws %}try {% endif %}{% call methodClosureName method %}.map({ {% if method.throws %}try {% endif %}$0({% call methodClosureCallParameters method %}) }) ?? {% call swiftifyMethodName method.selectorName %}ReturnValue
{% endif %}
}
{% endif %}
{% endmacro %}
{% macro mockOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }}
{% endmacro %}
{% macro mockNonOptionalArrayOrDictionaryVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} = {% if variable.isArray %}[]{% elif variable.isDictionary %}[:]{% endif %}
{% endmacro %}
{% macro mockNonOptionalVariable variable %}
var {% call mockedVariableName variable %}: {{ variable.typeName }} {
get { return {% call underlyingMockedVariableName variable %} }
set(value) { {% call underlyingMockedVariableName variable %} = value }
}
var {% call underlyingMockedVariableName variable %}: {{ variable.typeName }}!
{% endmacro %}
{% macro underlyingMockedVariableName variable %}underlying{{ variable.name|upperFirstLetter }}{% endmacro %}
{% macro mockedVariableName variable %}{{ variable.name }}{% endmacro %}
{% for type in types.protocols where type.name == "WebSocketClient" or type.name == "TimeoutTimer" %}
class {{ type.name }}Mock: {{ type.name }} {
{% for variable in type.allVariables|!definedInExtension %}
{% if variable.isOptional %}{% call mockOptionalVariable variable %}{% elif variable.isArray or variable.isDictionary %}{% call mockNonOptionalArrayOrDictionaryVariable variable %}{% else %}{% call mockNonOptionalVariable variable %}{% endif %}
{% endfor %}
{% for method in type.allMethods|!definedInExtension %}
{% call mockMethod method %}
{% endfor %}
}
{% endfor %}