Pusher Channels Swift REST API Library
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
Supported platforms
- Swift 5.3 and above
- Xcode 12.0 and above
Deployment targets
- iOS 13.0 and above
- macOS 10.15 and above
- tvOS 13.0 and above
- watchOS 6.0 and above
Installation
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.
Usage
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.
Configuration
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
See the discussion in End to end encryption for details on how to generate a secureencryptionMasterKey
.
Triggering events
To trigger an event on one or more channels, use the trigger(event:callback:)
method.
A single channel
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
}
}
Multiple channels
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
}
}
Event batches
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
}
}
Excluding receipients
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
}
}
Authenticating channel subscriptions
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.
Private channels
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
}
}
Presence channels
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
}
}
Verifying webhooks
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
}
}
End to end encryption
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.
Application state queries
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.
Fetch a list of occupied 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
}
}
Fetch information about a channel
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
.
Fetch a list of users subscribed to a presence channel
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
}
}
Documentation
Full documentation of the library can be found in the API docs.
Reporting bugs and requesting features
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.
Credits
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.
License
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).