A Swift library for interacting with the Pusher Channels HTTP API.
Register for a Pusher account, set up a Channels app and use the app credentials app as shown below.
- Supported platforms
- Installation
- Usage
- Documentation
- Reporting bugs and requesting features
- Credits
- License
- Swift 5.3 and above
- Xcode 12.0 and above
- iOS 13.0 and above
- macOS 10.15 and above
- tvOS 13.0 and above
- watchOS 6.0 and above
To integrate the library into your project using Swift Package Manager, you can add the library as a dependency in Xcode – see the docs. The package repository URL is:
https://github.com/pusher/pusher-http-swift.git
Alternatively, you can add the library as a dependency in your Package.swift
file. For example:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "YourPackage",
products: [
.library(
name: "YourPackage",
targets: ["YourPackage"]),
],
dependencies: [
.package(url: "https://github.com/pusher/pusher-http-swift.git",
.upToNextMajor(from: "0.1.0")),
],
targets: [
.target(
name: "YourPackage",
dependencies: ["Pusher"]),
]
)
You will then need to include an import Pusher
statement in any source files where you wish to use the library.
This section describes how to configure and use this library to act as an interface to your Pusher Channels app via the Channels HTTP API.
NOTES:
- Certain initializers or methods can throw an error if invalid parameters are provided, or an operation fails for some reason. The use of
try!
in the code examples shown below is for brevity and is not recommended for a production application.
Use the credentials from your Pusher Channels application to create a new Pusher
instance, which will act as your API client:
let pusher = Pusher(options: try! PusherClientOptions(appId: 123456,
key: "YOUR_APP_KEY",
secret: "YOUR_APP_SECRET",
encryptionMasterKey: "YOUR_BASE64_ENCODED_MASTER_KEY",
cluster: "YOUR_APP_CLUSTER"))
NOTES:
- See the discussion in End to end encryption for details on how to generate a secure
encryptionMasterKey
.
To trigger an event on one or more channels, use the trigger(event:callback:)
method.
let publicChannel = Channel(name: "my-channel", type: .public)
let publicEvent = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannel)
self.pusher.trigger(event: publicEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
let publicChannelOne = Channel(name: "my-channel", type: .public)
let publicChannelTwo = Channel(name: "my-other-channel", type: .public)
let multichannelEvent = try! Event(name: "my-multichannel-event",
data: "hello world!",
channels: [publicChannelOne,
publicChannelTwo])
self.pusher.trigger(event: publicEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
It's also possible to send multiple events with a single API call (max 10 events per call on multi-tenant clusters) using the trigger(events:callback:)
method:
let publicChannelOne = Channel(name: "my-channel", type: .public)
let publicChannelTwo = Channel(name: "my-other-channel", type: .public)
let eventOne = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannelOne)
let eventTwo = try! Event(name: "my-other-event",
data: "hello world, again!",
channel: publicChannelTwo)
self.pusher.trigger(events: [eventOne, eventTwo]]) { result in
switch result {
case .success(let channelInfoList):
// Inspect `channelInfoList`
case .failure(let error):
// Handle error
}
}
In some situations, you want to stop the client that broadcasts an event from receiving it. You can do this (by specifying its socketId
)[https://pusher.com/docs/channels/server_api/excluding-event-recipients] when triggering an event:
let socketIdToExclude = "123.456"
let publicChannel = Channel(name: "my-channel", type: .public)
let excludedClientEvent = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannel,
socketId: socketIdToExclude)
self.pusher.trigger(event: excludedClientEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
Fetching channel attributes on triggering events [EXPERIMENTAL]
It is possible to fetch attributes about the channel(s) that were triggered to with the attributeOptions
parameter on Event
. This works with both trigger(…)
methods:
let publicChannel = Channel(name: "my-channel", type: .public)
let publicEvent = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannel,
attributeOptions: [.subscriptionCount])
self.pusher.trigger(event: publicEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
NOTES:
- The
trigger(…)
method is asynchronous. If you are not using this in a GUI application, you may need to use a semaphore:
let publicChannel = Channel(name: "my-channel", type: .public)
let publicEvent = try! Event(name: "my-event",
data: "hello world!",
channel: publicChannel)
let sema = DispatchSemaphore(value: 0)
pusher.trigger(event: publicEvent) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries
case .failure(let error):
// Handle error
}
sema.signal()
}
sema.wait()
Users that attempt to subscribe to a private or presence channel must be first authenticated. An authentication token that can be returned to a user client that is attempting a subscription, which requires authentication with the server.
To authenticate a user that is attempting to subscribe to a private channel, you can use the authenticate(channel:socketId:callback:)
method:
let userSocketId = "123.456"
let privateChannel = Channel(name: "my-channel", type: .private)
self.pusher.authenticate(channel: privateChannel,
socketId: userSocketId) { result in
switch result {
case .success(let authToken):
// Inspect `authToken`
case .failure(let error):
// Handle error
}
}
To authenticate a user that is attempting to subscribe to a presence channel, you must provide a userData
parameter to the same method:
let userData = PresenceUserAuthData(userId: "USER_ID", userInfo: ["name": "Joe Bloggs"])
let presenceChannel = Channel(name: "my-channel", type: .presence)
self.pusher.authenticate(channel: presenceChannel,
socketId: "USER_SOCKET_ID",
userData: userData) { result in
switch result {
case .success(let authToken):
// Inspect `authToken`
case .failure(let error):
// Handle error
}
}
This library provides a way to verify that a received webhook request is genuine and was received from Pusher. Since a webhook endpoint is accessible to the global internet, verifying that webhook request originated from Pusher is important. Valid webhooks contain special headers which contain a copy of your application key and a HMAC signature of the webhook payload (i.e. its body):
self.pusher.verifyWebhook(request: receivedWebhookRequest) { result in
switch result {
case .success(let webhook):
// Inspect `webhook`
case .failure(let error):
// Handle error
}
}
This library supports end-to-end encryption of your private channels. This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them. You can enable this feature by following these steps:
-
You should first set up private channels. This involves creating an authentication endpoint on your server.
-
Next, generate your 32 byte master encryption key, encode it as Base-64 and pass it to the
PusherClientOptions
initializer. This is secret and you should never share this with anyone, not even Pusher.
openssl rand -base64 32
let options = try! PusherClientOptions(appId: 123456,
key: "YOUR_APP_KEY",
secret: "YOUR_APP_SECRET",
encryptionMasterKey: "<MASTER KEY GENERATED BY PREVIOUS COMMAND>",
cluster: "YOUR_APP_CLUSTER")
-
Channels where you wish to use end-to-end encryption should be of type
encrypted
. -
Subscribe to these channels in your client, and you're done! You can verify it is working by checking out the debug console on the https://dashboard.pusher.com/ and seeing the scrambled ciphertext.
Important note: This will not encrypt messages on channels that are not of type encrypted
.
Limitation: you cannot trigger a single event on multiple channels in a call to the trigger(event:callback:)
method, e.g:
let publicChannel = Channel(name: "my-channel", type: .public)
let encryptedChannel = Channel(name: "my-other-channel", type: .encrypted)
let event = try! Event(name: "my-event",
data: "hello world!",
channels: [publicChannel, encryptedChannel])
self.pusher.trigger(event: event]) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
Rationale: the methods in this library map directly to individual Channels HTTP API requests. If we allowed triggering a single event on multiple channels (some encrypted, some unencrypted), then it would require two API requests: one where the event is encrypted to the encrypted channels, and one where the event is unencrypted for unencrypted channels.
Information about the current state of your Channels application can be fetched using the library. This includes the state of occupied channels, and users subscribed to presence channels.
A list of any occupied channels for your Channels application can be fetched using the channels(withFilter:attributeOptions:callback:)
method:
// Fetching all occupied channels
self.pusher.channels { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
// Fetching only occupied private channels
self.pusher.channels(withFilter: .private) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
// Fetching all occupied presence channels (with user counts)
self.pusher.channels(withFilter: .presence,
attributeOptions: .userCount) { result in
switch result {
case .success(let channelSummaries):
// Inspect `channelSummaries`
case .failure(let error):
// Handle error
}
}
Information about a channel for your Channels application can be fetched using the channelInfo(for:attributeOptions:callback:)
method:
// Fetch information for a public channel
let publicChannel = Channel(name: "my-channel", type: .public)
self.pusher.channelInfo(for: publicChannel) { result in
switch result {
case .success(let channelInfo):
// Inspect `channelInfo`
case .failure(let error):
// Handle error
}
}
// Fetch information for a private channel (with subscription count)
let privateChannel = Channel(name: "my-channel", type: .private)
self.pusher.channelInfo(for: privateChannel,
attributeOptions: [.subscriptionCount]) { result in
switch result {
case .success(let channelInfo):
// Inspect `channelInfo`
case .failure(let error):
// Handle error
}
}
// Fetch information for a presence channel (with user count)
let presenceChannel = Channel(name: "my-channel", type: .presence)
self.pusher.channelInfo(for: presenceChannel,
attributeOptions: [.userCount]) { result in
switch result {
case .success(let channelInfo):
// Inspect `channelInfo`
case .failure(let error):
// Handle error
}
}
NOTES:
- If the specified channel is not occupied (i.e. it has no subscribers), then the returned
ChannelInfo
object will not contain any attributes (regardless of if they were requested) and itsisOccupied
property will be set tofalse
.
A list of users subscribed to a presence channel for your Channels application can be fetched using the users(for:callback:)
method:
let presenceChannel = Channel(name: "my-channel", type: .presence)
self.pusher.users(for: presenceChannel) { result in
switch result {
case .success(let users):
// Inspect `users`
case .failure(let error):
// Handle error
}
}
Full documentation of the library can be found in the API docs.
Please ensure you use the relevant issue template when reporting a bug or requesting a new feature. Also please check first to see if there is an open issue that already covers your bug report or new feature request.
This library is owned and maintained by Pusher. It was originally created by Daniel Browne.
It uses code from the following third-party repositories:
The individual licenses for these libraries are included in the corresponding Swift files.
The library is completely open source and released under the MIT license. See LICENSE for details if you want to use it in your own project(s).