yosefabraham / swamp Goto Github PK
View Code? Open in Web Editor NEWthe WAMP WebSocket subprotocol implemented purely in Swift using Starscream & SwiftyJSON
License: MIT License
the WAMP WebSocket subprotocol implemented purely in Swift using Starscream & SwiftyJSON
License: MIT License
It took me a while to figure out how to get this working nicely, so I thought I'd share some tips. If you want to easily serialize or ingest JSON objects through the WAMP connection the serializer can make it hard to accomplish. Many of the available frameworks want to operate directly on JSON data, or spit it out as JSON, but swamp only exposes Arrays/Dictionaries.
Fortunately I found Wrap and Unbox. Wrap is an object mapper that translates class properties into a Dictionary that can be passed to SwiftyJSON. Unbox is used with JSON, but can also accept a Dictionary to rehydrate your classes. Both handle optionals and dates and custom behaviors.
Step 1: Wrap your objects. You can do this when you are building arguments for your RPC calls, or you can do it within swamp using the following technique:
// declare a protocol or class your objects will conform to
class WampMessage: Unboxable {
}
// override _JSONSwampSerializer_ to look for your custom class/protocol
class WampMessageSwampSerializer: JSONSwampSerializer {
open func packArray(_ arrayData:[Any]) -> [JSON] {
var jsonArray = [JSON]()
for item in arrayData {
if let arrayItem = item as? Array<Any> {
let newArray = packArray(arrayItem)
jsonArray.append(JSON(newArray))
} else {
do {
if let wampItem = item as? WampMessage {
let dict: [String:Any] = try wrap(wampItem)
jsonArray.append(JSON(dict))
} else {
jsonArray.append(JSON(item))
}
} catch {
print("Fatal exception while packing `\(item)` into JSON")
}
}
}
return jsonArray
}
override func pack(_ data: [Any]) -> Data? {
let json = JSON(packArray(data))
do {
return try json.rawData()
}
catch let err as NSError {
print("error = \(err.localizedDescription)")
return nil
}
}
}
Step 2: Unbox the results. This example shows a WampMessage being passed in (see Step 1), but you could just as easily use bare JSON or Swift types, or convert the object inline by doing something like: let arg3: [String:Any] = try wrap(MyWampMessageObject() )
In the results block it shows a new object being initialized from the results using Unbox. If you didn't know the object type from the route being used, you might have to look at a value in the results first in order to determine what kind of object to Unbox
let wampArgs: [Any] = [ arg1, arg2, MyWampMessageObject() ]
swampSession.call("my.route", options: [:], args: wampArgs, kwargs: nil, onSuccess: { details, results, kwResults in
if let results = results, let dict = results[0] as? [String:Any] {
do {
let userInfo: MyWampMessageResult = try unbox(dictionary: dict)
} catch {
print("failed to decode userInfo")
}
}
}, onError: { details, error, args, kwargs in
print ("ERROR: details: \(details), error: \(error), kwargs: \(kwargs)")
})
Please update cocoa pod spec
I use a function in the background to generate the challenge and am wondering if there is a version of the swampSessionHandleChallenge method that will allow for an async response.
I'm curious if you are already working on migrating to Swift 3, have plans to do so, or would accept a PR for same.
Ditto for Carthage support.
I have a working WAMP implementation that crashes every 50 times or so. It happens when I leave the screen and the disconnect() method gets called:
class WampConnector: NSObject {
static var authid = ""
let wampEndpoint = "ws://wamp.workspace-beta.nl:9090"
let realm = "workspace"
let swampTransport: WebSocketSwampTransport
let swampSession: SwampSession
init?(withDelegate delegate: SwampSessionDelegate) {
let url = URL(string: wampEndpoint) ?? URL(fileURLWithPath: "")
swampTransport = WebSocketSwampTransport(wsEndpoint: url)
swampSession = SwampSession(realm: realm, transport: swampTransport, authmethods: ["ticket"])
swampSession.delegate = delegate
swampSession.connect()
super.init()
}
func disconnect() {
swampSession.disconnect("Leaving screen")
}
}
The error occurs when force-unwrapping the serializer and/or data.
fileprivate func sendMessage(_ message: SwampMessage){
let marshalledMessage = message.marshal()
let data = self.serializer!.pack(marshalledMessage as [Any])!
self.transport.sendData(data)
}
I notice force unwrapping is the default way to handle optionals in the library, probably to ensure errors get caught early instead of just returning after a guard statement and failing silently. But I think this error occurs because of some kind of race condition that's really hard to reproduce and it might be safer to guard let everything and return when the serializer is gone or no data instance is created.
Hello,
Thanks for such a wonderful library. Can you update your pod specification to provide support for watch os too? Thanks.
I am working on a project, which need to handle wss instead of ws. Do I need additional work for wss? Moreover, does Swamp 0.2.0 support WAMP 1.0 server?
@iscriptology I'm not sure the best way to proceed with this. I need to get SWAMP working quickly so I can move on to other things. I would like to be able to work with you and create a more robust implementation, but as I said in my PR for Carthage support, my patches are going to all depend on each other.
If you don't have spare time to work on this right now, I'll proceed with development in my own fork, and maybe at some point in the future we can work together to merge the two.
Here's my short term plan:
add enums for WAMP protocol and auth method
add separate WebSocketSwampTransport initializers for JSON and MsgPack
fix wampcra support for salted passwords
remove cryptoswift, replace with CommonCrypto (CryptoSwift is a very heavy dependency for one single call to HMAC)
I can't figure out if is possible to advertise multiple protocols [json, msgpack] and get enough information back via the websocket to set the mode and serializer properly, but it would be nice to have that happen programmatically.
Hi! I have server with rooms to chat. Full url is ws://93.171.XXX.XX:8080/conversation_realtime/sportspace581a1541590b21.14581077".
But I have to connect to this by 2 steps:
In JS it looks like:
var _WS_URI = "ws://93.171.242.80:8080";
var webSocket = WS.connect(_WS_URI);
webSocket.on("socket/connect", function(session){
session.subscribe("conversation_realtime/sportspace581a1541590b21.14581077", function(uri, payload){
console.log(payload.message.text);
}
}
When I try to call in my project swampSession.call("socket/connect"...
or swampSession.subscribe("conversation_realtime/sportspace581a1541590b21.14581077"....
my app crashes in SwampSession.swift with error "fatal error: unexpectedly found nil while unwrapping an Optional value" in this part:
fileprivate func sendMessage(_ message: SwampMessage){
let marshalledMessage = message.marshal()
let data = self.serializer!.pack(marshalledMessage as [Any])!
self.transport.sendData(data)
}
How can I realize it in your library? And what is "realm" in my case?
Please give an update for swift 4
I have a scenario where I must issue multiple RPCs via swamp and then wait for all of them to complete before taking another step. I solved my use case with a DispatchGroup in manual mode (calling DispatchGroup.join() before issuing swampSession.call, calling DispatchGroup.leave() in the closures, and then using DispatchGroup.notify to wait for all issued calls to finish.
This mostly works fine when the DispatchGroup is managed from the main thread, or larded up with debug statements. But occasionally it hangs; not all of the RPC calls succeed so the group is never notified.
I tracked the problem down to the requestIds of the various RPC calls. Because swift variables are not atomic, the default implementation here is not thread safe. Multiple threads can increment the counter and then overwrite the result.
fileprivate func generateRequestId() -> Int {
self.currRequestId += 1
return self.currRequestId
}
This tweaked version uses an unfair lock to make the increment thread safe. It's pretty much the best option for Swift 3. Note that the stack variable is necessary otherwise another thread could increment the requestId after the lock is released, before it is returned.
private var unfair_lock = os_unfair_lock_s()
fileprivate func generateRequestId() -> Int {
os_unfair_lock_lock(&unfair_lock)
let newRequestId = self.currRequestId + 1
self.currRequestId = newRequestId
os_unfair_lock_unlock(&unfair_lock)
return newRequestId
}
I am not certain that there are no other threading issues, but this appears to have fixed my one issue nicely.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.