• Stars
    star
    409
  • Rank 101,834 (Top 3 %)
  • Language
    C
  • License
    Other
  • Created about 14 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An Objective-C interface to Pusher Channels

Pusher Channels Objective-C Client

Build Status

libPusher is an Objective-C client library for the Pusher Channels realtime service.

Pusher Channels is a hosted service that sits between your web application and the browser that lets you deliver events in realtime using WebSockets.

The libPusher API mirrors the Pusher Channels JavaScript client as closely as possible, with some allowances for Objective-C conventions. In particular, whilst the JavaScript client uses event binding for all event handling, where events are pre-defined, libPusher uses the standard Cocoa delegation pattern.

API Documentation

Example

Subscribe to the chat channel and bind to the new-message event.

// self.client is a strong instance variable of class PTPusher
self.client = [PTPusher pusherWithKey:@"YOUR_API_KEY" delegate:self encrypted:YES];

// subscribe to channel and bind to event
PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];
[channel bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *channelEvent) {
	// channelEvent.data is a NSDictianary of the JSON object received
    NSString *message = [channelEvent.data objectForKey:@"text"];
	NSLog(@"message received: %@", message);
}];

[self.client connect];

Supported platforms

Deployment targets

  • iOS 6.0 and above
  • macOS (OS X) 10.9 and above

Installation

Install using CocoaPods is recommended.

pod 'libPusher', '~> 1.6.4'

Import Pusher into the class that wants to make use of the library.

#import <Pusher/Pusher.h>

If you want to use the ReactiveExtensions version of libPusher, add the following line to your Podfile instead.

pod 'libPusher/ReactiveExtensions', '~> 1.6.4'

This will also load the core libPusher library and ReactiveCocoa as a dependency.

If you are not using CocoaPods, you can simply drop the extensions into your project.

Usage

Note: in the following examples, client is a strong property. The instance returned by the pusherWithKey:*: methods will be auto-released, according to standard Objective-C return conventions. You must retain the client otherwise it will be auto-released before anything useful happens causing silent failures and unexpected behaviour.

Create a client and connecting

self.client = [PTPusher pusherWithKey:@"YOUR_API_KEY" delegate:self encrypted:YES];

[self.client connect];

Note that clients do not connect automatically (as of version 1.5). You are responsible for calling connect as needed.

It is recommended to implement the PTPusherDelegate protocol in order to be notified when significant connection events happen such as connection errors, disconnects, and retries.

When the Pusher app is created in a different cluster to the default cluster, you must specify the cluster parameter.

self.client = [PTPusher pusehrWithKey:@"YOUR_API_KEY" delegate:self encrypt:YES cluster:@"YOUR_APP_CLUSTER"]

[self.client connect];

Subscribe to channels

Channels are a way of filtering the events you want your application to receive. In addition, private and presence channels can be used to control access to events and in the case of presence channels, see who else is subscribing to events. For more information on channels, see the documentation.

There is no need to wait for the client to establish a connection before subscribing. You can subscribe to a channel immediately and any subscriptions will be created once the connection has connected.

Subscribing to public channels

PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];

Subscribing to private channels

This method will add the appropriate private- prefix to the channel name for you and return a channel cast to the correct PTPusherChannel subclass PTPusherPrivateChannel.

Subscribing to private channels needs server-side authorization. See section Channel authorization for details.

// subscribe to private-chat channel
PTPusherPrivateChannel *private = [self.client subscribeToPrivateChannelNamed:@"chat"];

Subscribing to presence channels

This method will add the appropriate presence- prefix to the channel name for you and return a channel cast to the correct PTPusherChannel subclass PTPusherPresenceChannel.

Subscribing to presence channels needs server-side authorization. See section Channel Authorization for details.

// subscribe to presence-chat channel
PTPusherPresenceChannel *presence = [client subscribeToPresenceChannelNamed:@"chat" delegate:self];

It is recommended to implement PTPusherPresenceChannelDelegate protocol, to receive notifications for members subscribing or unsubscribing from the presence channel.

Accessing subscribed channels

You can use the channelNamed: method to retrieve an existing subscribed channel. If you have not subscribed to the requested channel, it will return nil.

// get the 'chat' channel that you've already subscribed to
PTPusherChannel *channel = [self.client channelNamed:@"chat"];

Unsubscribe from channels

If you no longer want to receive events on a given channel, you can unsubscribe from it.

PTPusherChannel *channel = [self.client channelNamed:@"chat"];
[channel unsubscribe];

Channel object lifetime

When the client disconnects, all subscribed channels are implicitly unsubscribed (isSubscribed will return NO), however the channel objects will persist and so will any event bindings.

When the client reconnects, all previously subscribed channels will be resubscribed (which might involve another authentication request for any private/presence channels) and your existing event bindings will continue working as they did prior to the disconnection.

If you explicitly unsubscribe from a channel, all event bindings will be removed and the client will remove the channel object from its list of subscribed channels. If no other code has a strong reference to the channel object, it will be deallocated. If you resubscribe to the channel, a new channel object will be created. You should bear this in mind if you maintain any strong references to a channel object in your application code.

Channel authorization

Private and presence channels require server-side authorization before they can connect.

Note: Make sure your server responds correctly to the authentication request. See the authentication signature and user authentication docs for details and examples on how to implement authorization on the server side.

In order to connect to a private or presence channel, you first need to configure your server authorization URL.

self.client.authorizationURL = [NSURL URLWithString:@"https://www.yourserver.com/authorize"];

When you attempt to connect to a private or presence channel, libPusher will make a form-encoded POST request to the above URL, passing along the socket_id and channel_name as parameters. Prior to sending the request, the Pusher delegate will be notified, passing in the channel and the NSMutableURLRequest instance that will be sent.

Custom authorization

In order to perform custom authorization, you need to assign the channelAuthorizationDelegate and implement the PTPusherChannelAuthorizationDelegate protocol, which currently contains a single method. The following example uses the AFNetworking library to perform server-based authorization:

- (BOOL)applicationDidFinishLaunching:(UIApplication *)application
{
  ...
  self.pusherClient.channelAuthorizationDelegate = self;
}

- (void)pusherChannel:(PTPusherChannel *)channel requiresAuthorizationForSocketID:(NSString *)socketID completionHandler:(void(^)(BOOL isAuthorized, NSDictionary *authData, NSError *error))completionHandler
{
  NSDictionary *authParameters = :@{@"socket_id": socketID, @"channel_name": channel.name};

  [[MyHTTPClient sharedClient] postPath:@"/pusher/auth" parameters:authParameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
      completionHandler(YES, responseObject, nil);
  } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    completionHandler(NO, nil, error);
  }];
}

Binding to events

There are generally two ways to bind to events: binding to an event on the PTPusher client itself or binding to a specific channel.

Two types of direct binding are supported: target/action and block-based bindings. The examples below using block-based bindings.

Binding to a channel

Once you have created an instance of PTPusherChannel, you can set up event bindings. There is no need to wait for the PTPusher client connection to be established or the channel to be subscribed.

When you bind to events on a single channel, you will only receive events with that name if they are sent over this channel.

PTPusherChannel *channel = [self.client subscribeToChannelNamed:@"chat"];
[channel bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *channelEvent) {
  // channelEvent.data is a NSDictionary of the JSON object received
}];

Binding directly to the client

When you bind to events on the client, you will receive all events with that name, regardless of the channel from which they originated.

[self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
  // event.data is a NSDictionary of the JSON object received
}];

Remove bindings

If you no longer want to receive events with a specific name, you can remove the binding. Removing a binding is as simple as storing a reference to the binding object, then passing that as an argument to removeBinding: at a later point.

Note: Binding objects are owned by the client or channel that they relate to and will exist for the lifetime of the binding. For this reason, you generally only need to store a weak reference to the binding object in order to remove the binding. In the event that something else removes the binding (perhaps as a result of calling removeAllBindings or explicitly unsubscribing from the channel), the weak reference will ensure that the binding object will become nil, so you should check this before calling removeBinding:.

// _binding is a weak reference
_binding = [self.client bindToEventNamed:@"new-message" target:self action:@selector(handleEvent:)];

// check that nothing else has removed the binding already
if (_binding) {
  [self.client removeBinding:_binding];
}

Memory management considerations for block-based bindings

Similar caveats apply to block-based bindings as they do to using block based NSNotificationCenter observers, i.e. when referencing self in your event handler block, it is possible in some situations to create retain cycles or prevent self from being deallocated.

When you reference self in your event handler block, the block will retain a strong reference to self. This means that self will never be deallocated until the binding (and in turn the event handler block) is destroyed by removing the binding. For this reason, you should be wary about removing event bindings in dealloc methods as dealloc will never be called if the binding references self.

For example, you might push a UIViewController on to a UINavigationController stack, then create an event binding in that view controller's viewDidAppear: method:

- (void)viewDidAppear:(BOOL)animated
{
  // _binding is a weak instance variable
  _binding = [self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
    [self doSomethingWithEvent:event];
  }];
}

If you were to then pop that view controller off the navigation stack without removing the event binding, because the binding block has a strong reference to self, the view controller will never be deallocated and you will have a memory leak.

You can handle this in one of two ways. The first is to ensure you remove the binding when in the corresponding viewDidDisappear:

- (void)viewDidDisappear:(BOOL)animated
{
  [self.client removeBinding:_binding];
}

The second, is to prevent a strong reference to self being captured in the first place:

- (void)viewDidAppear:(BOOL)animated
{
  __weak typeof(self) weakSelf = self;

  // _binding is a weak instance variable
  _binding = [self.client bindToEventNamed:@"new-message" handleWithBlock:^(PTPusherEvent *event) {
    __strong typeof(weakSelf) strongSelf = weakSelf;
    [strongSelf doSomethingWithEvent:event];
  }];
}

Finally, if you reference self in a block and store a strong reference to the binding object, you will create a retain cycle (self -> binding -> block -> self). You should avoid keeping strong references to binding objects but if you really need to, you should ensure you only capture a weak reference to self in the block as in the above example.

Binding to all events

In some cases it might be useful to bind to all events of a client or channel. libPusher will publish a NSNotification for every event received. You can subscribe to all events for a client or channel by adding a notification observer.

Binding to all events using NSNotificationCenter:

[[NSNotificationCenter defaultCenter]
          addObserver:self
             selector:@selector(didReceiveEventNotification:)
                 name:PTPusherEventReceivedNotification
               object:self.client];

Bind to all events on a single channel:

// get chat channel
PTPusherChannel *channel = [self.client channelNamed:@"chat"];

[[NSNotificationCenter defaultCenter]
          addObserver:self
             selector:@selector(didReceiveChannelEventNotification:)
                 name:PTPusherEventReceivedNotification
               object:channel];

The event can be retrieved in your callback from the notification's userInfo dictionary. The notification's object will be either the client or channel from which the event originated.

- (void)didReceiveEventNotification:(NSNotification *)notification
{
	PTPusherEvent *event = [notification.userInfo objectForKey:PTPusherEventUserInfoKey];
}

Handling network connectivity errors and disconnects

The nature of a mobile device is that connections will come and go. There are a number of things you can do do ensure that your Pusher connection remains active for as long as you have a network connection and reconnects after network connectivity has been re-established.

Automatic reconnection behaviour

libPusher will generally try and do its best to keep you connected in most cases:

  • If the connection fails having been previously connected, the client will try and reconnect immediately.
  • If the connection disconnects with a Pusher error code in the range 4200-4299, the client will try and reconnect immediately.
  • If the connection disconnects with a Pusher error code in the range 4100-4199, the client will try and reconnect with a linear back-off delay.
  • If the connection disconnects for an unknown reason, the client will try and reconnect after a configured delay (defaults to 5 seconds and can be changed using the reconnectDelay property).

All automatic reconnection attempts will be repeated up to a maximum limit before giving up.

Automatic reconnection will not happen in the following situations:

  • The connection fails on the initial attempt (i.e. not previously connected)
  • The connection disconnects with a Pusher error code in the range 4000-4099 (indicating a client error, normally a misconfiguration)
  • The maximum number of automatic reconnection attempts have been reached

An error code in the range 4000-4099 generally indicates a client misconfiguration (e.g. invalid API key) or rate limiting. See the Pusher protocol documentation for more information.

The other scenarios generally indicate that it is not currently possible to connect to the Pusher service - this might be because of an issue with the service but more likely is that there simply isn't an internet connection.

Up to version 1.6, automatic reconnection would happen after the configured reconnectDelay even after explicitly calling disconnect. This behaviour was undesirable and in all subsequent versions this no longer happens and an explicit call to connect is required to reconnect in this case.

Handling disconnections

If the client fails to connect at all, the delegate method pusher:connection:failedWithError: will be called and no automatic reconnection will be attempted.

If the client disconnects, the delegate method pusher:connection:didDisconnectWithError:willAttemptReconnect: will be called. If willAttemptReconnect is YES, you don't have any further work to do.

If willAttemptReconnect is NO, you should first check the error to see if there is a client misconfiguration. If the client is refusing to automatically reconnect due to a Pusher error code, the NSError will have a domain of PTPusherFatalErrorDomain.

How you handle disconnections is up to you, but the general idea is to check if there is network connectivity and if there is not, wait until there is before reconnecting.

It is recommended that you let the OS handle connection management. In practical terms this means that if you want to ensure that a client won't have any further messages sent to it when they have backgrounded the app then you should call unsubscribe on the relevant channel(s) as opposed to calling disconnect. Eventually the connection will be cleaned up (and therefore closed) by the OS. Trying to explicitly call disconnect yourself upon backgrounding can lead to unexpected behaviour with multiple connections then being opened upon the app being foregrounded again.

Example: handling disconnections using the Reachability library

In this example, we first check for any fatal Pusher errors, before using Reachability to wait for an internet connection to become available before manually reconnecting.

- (void)pusher:(PTPusher *)pusher connection:(PTPusherConnection *)connection failedWithError:(NSError *)error
{
  [self handleDisconnectionWithError:error];
}

- (void)pusher:(PTPusher *)pusher connection:(PTPusherConnection *)connection didDisconnectWithError:(NSError *)error willAttemptReconnect:(BOOL)willAttemptReconnect
{
  if (!willAttemptReconnect) {
    [self handleDisconnectionWithError:error];
  }
}

The implementation of handleDisconnectionWithError performs the error check and waits for Reachability to change:

- (void)handleDisconnectionWithError:(NSError *)error
{
  Reachability *reachability = [Reachability reachabilityWithHostname:self.client.connection.URL.host];

  if (error && [error.domain isEqualToString:PTPusherFatalErrorDomain]) {
    NSLog(@"FATAL PUSHER ERROR, COULD NOT CONNECT! %@", error);
  }
  else {
    if ([reachability isReachable]) {
      // we do have reachability so let's wait for a set delay before trying again
      [self.client performSelector:@selector(connect) withObject:nil afterDelay:5];
    }
    else {
      // we need to wait for reachability to change
      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(_reachabilityChanged:)
                                                   name:kReachabilityChangedNotification
                                                 object:reachability];

      [reachability startNotifier];
    }
  }
}

- (void)_reachabilityChanged:(NSNotification *note)
{
  Reachability *reachability = [note object];

  if ([reachability isReachable]) {
    // we're reachable, we can try and reconnect, otherwise keep waiting
    [self.client connect];

    // stop watching for reachability changes
    [reachability stopNotifier];

    [[NSNotificationCenter defaultCenter]
        removeObserver:self
                  name:kReachabilityChangedNotification
                object:reachability];
  }
}

For a more sophisticated implementation of handling client disconnections and to see how this integrates with a real application, you could take a look at the ClientDisconnectionHandler class in the official Pusher iOS Diagnostics app.

License

All code is licensed under the MIT license. See the LICENSE file for more details.

More Repositories

1

pusher-js

Pusher Javascript library
JavaScript
1,970
star
2

atom-pair

An Atom package that allows for epic pair programming
JavaScript
1,454
star
3

pusher-http-php

PHP library for interacting with the Pusher Channels HTTP API
PHP
1,355
star
4

pusher-http-ruby

Ruby library for Pusher Channels HTTP API
Ruby
659
star
5

pusher-http-laravel

[DEPRECATED] A Pusher Channels bridge for Laravel
PHP
405
star
6

pusher-http-python

Pusher Channels HTTP API library for Python
Python
368
star
7

k8s-spot-rescheduler

Tries to move K8s Pods from on-demand to spot instances
Go
311
star
8

pusher-websocket-java

Pusher Channels client library for Java targeting general Java and Android
Java
302
star
9

pusher-websocket-swift

Pusher Channels websocket library for Swift
Swift
267
star
10

build-a-slack-clone-with-react-and-pusher-chatkit

In this tutorial, you'll learn how to build a chat app with React, complete with typing indicators, online status, and more.
JavaScript
235
star
11

pusher-angular

Pusher Angular Library | owner=@leesio
JavaScript
233
star
12

pusher-http-go

Pusher Channels HTTP API library for Go
Go
196
star
13

k8s-spot-termination-handler

Monitors AWS for spot termination notices when run on spot instances and shuts down gracefully
Makefile
118
star
14

NWWebSocket

A WebSocket client written in Swift, using the Network framework from Apple.
Swift
112
star
15

go-interface-fuzzer

Automate the boilerplate of fuzz testing Go interfaces | owner: @willsewell
Go
110
star
16

pusher-http-dotnet

.NET library for interacting with the Pusher HTTP API
C#
109
star
17

k8s-auth-example

Example Kubernetes Authentication helper. Performs OIDC login and configures Kubectl appropriately.
Go
108
star
18

pusher-websocket-dotnet

Pusher Channels Client Library for .NET
C#
107
star
19

faros

Faros is a CRD based GitOps controller
Go
100
star
20

backbone-todo-app

JavaScript
92
star
21

chatkit-client-js

JavaScript client SDK for Pusher Chatkit
JavaScript
89
star
22

quack

In-Cluster templating for Kubernetes manifests
Go
70
star
23

pusher-channels-flutter

Pusher Channels client library for Flutter targeting IOS, Android, and WEB
Dart
67
star
24

websockets-from-scratch-tutorial

Tutorial that shows how to implement a websocket server using Ruby's built-in libs
Ruby
60
star
25

backpusher

JavaScript
54
star
26

push-notifications-php

Pusher Beams PHP Server SDK
PHP
54
star
27

chatkit-android

Android client SDK for Pusher Chatkit
Kotlin
54
star
28

pusher-websocket-react-native

React Native official Pusher SDK
TypeScript
53
star
29

django-pusherable

Real time notification when an object view is accessed via Pusher
Python
52
star
30

notify

Ruby
51
star
31

cli

A CLI for Pusher (beta)
Go
50
star
32

k8s-spot-price-monitor

Monitors the spot prices of instances in a Kubernetes cluster and exposes them as prometheus metrics
Python
44
star
33

chatkit-command-line-chat

A CLI chat, built with Chatkit
JavaScript
41
star
34

pusher-http-java

Java client to interact with the Pusher HTTP API
Java
40
star
35

chatkit-swift

Swift SDK for Pusher Chatkit
Swift
40
star
36

electron-desktop-chat

A desktop chat built with React, React Desktop and Electron
JavaScript
39
star
37

push-notifications-web

Beams Browser notifications
JavaScript
38
star
38

crank

Process slow restarter
Go
37
star
39

pusher-websocket-android

Library built on top of pusher-websocket-java for Android. Want Push Notifications? Check out Pusher Beams!
Java
35
star
40

chameleon

A collection of front-end UI components used across Pusher ✨
CSS
35
star
41

chatkit-server-php

PHP SDK for Pusher Chatkit
PHP
35
star
42

cide

Isolated test runner with Docker
Ruby
33
star
43

push-notifications-swift

Swift SDK for the Pusher Beams product:
Swift
33
star
44

pusher-phonegap-android

JavaScript
30
star
45

push-notifications-python

Pusher Beams Python Server SDK
Python
30
star
46

pusher-websocket-unity

Pusher Channels Unity Client Library
C#
27
star
47

hacktoberfest

24
star
48

laravel-chat

PHP
22
star
49

push-notifications-android

Android SDK for Pusher Beams
Kotlin
21
star
50

push-notifications-node

Pusher Beams Node.js Server SDK
JavaScript
20
star
51

pusher-test-iOS

iOS app for developers to test connections to Pusher
Objective-C
19
star
52

push-notifications-ruby

Pusher Beams Ruby Server SDK
Ruby
19
star
53

chatkit-server-node

Node.js SDK for Pusher Chatkit
TypeScript
16
star
54

rack-headers_filter

Remove untrusted headers from Rack requests | owner=@zimbatm
Ruby
15
star
55

pusher-test-android

Test and diagnostic app for Android, based on pusher-java-client
Java
14
star
56

pusher-realtime-tfl-cameras

Realtime TfL Traffic Camera API, powered by Pusher
JavaScript
14
star
57

buddha

Buddha command execution and health checking | owner: @willsewell
Go
14
star
58

chatkit-server-go

Chatkit server SDK for Golang
Go
13
star
59

pusher-channels-auth-example

A simple server exposing a pusher auth endpoint
JavaScript
13
star
60

pusher-platform-js

Pusher Platform client library for browsers and react native
TypeScript
13
star
61

stronghold

[DEPRECATED] A configuration service | owner: @willsewell
Haskell
12
star
62

pusher-twilio-example

CSS
12
star
63

prom-rule-reloader

Watches configmaps for prometheus rules and keeps prometheus in-sync
Go
12
star
64

sample-chatroom-ios-chatkit

How to make an iOS Chatroom app using Swift and Chatkit
PHP
11
star
65

electron-desktop-starter-template

JavaScript
11
star
66

chatkit-server-ruby

Ruby server SDK for Chatkit
Ruby
11
star
67

realtime-visitor-tracker

Realtime location aware visitor tracker for a web site or application
PHP
11
star
68

push-notifications-server-java

Pusher Beams Java Server SDK
Kotlin
10
star
69

android-slack-clone

Android chat application, built with Chatkit
Kotlin
10
star
70

filtrand

JavaScript
10
star
71

vault

Front-end pattern library
Ruby
9
star
72

push-notifications-go

Pusher Beams Go Server SDK
Go
9
star
73

pusher-platform-android

Pusher Platform SDK for Android
Kotlin
9
star
74

git-store

Go git abstraction for use in Kubernetes Controllers
Go
8
star
75

pusher-platform-swift

Swift SDK for Pusher platform products
Swift
8
star
76

realtime_survey_complete

JavaScript
8
star
77

docs

The all new Pusher docs, powered by @11ty and @vercel
CSS
8
star
78

push-notifications-server-swift

Pusher Beams Swift Server SDK
Swift
8
star
79

pusher-python-rest

Python client to interact with the Pusher REST API. DEPRECATED in favour of https://github.com/pusher/pusher-http-python
Python
8
star
80

real-time-progress-bar-tutorial

Used inthe realtime progress bar tutorial blog post - http://blog.pusher.com
JavaScript
7
star
81

pusher-channels-chunking-example

HTML
7
star
82

pusher-http-swift

Swift library for interacting with the Pusher Channels HTTP API
Swift
7
star
83

feeds-client-js

JS client for Pusher Feeds
JavaScript
6
star
84

pusher-test

Simple website which allows manual testing of pusher-js versions
JavaScript
6
star
85

java-websocket

A fork of https://github.com/TooTallNate/Java-WebSocket | owner=@zmarkan
HTML
6
star
86

bridge-troll

A Troll that ensures files don't change
Go
5
star
87

navarchos

Node replacing controller
Go
5
star
88

realtime-notifications-tutorial

Create realtime notifications in minutes, not days =)
4
star
89

pusher-socket-protocol

Protocol for pusher sockets
HTML
4
star
90

icanhazissues

Github issues kanban
JavaScript
4
star
91

textsync-server-node

[DEPRECATED] A node.js library to simplify token generation for TextSync authorization endpoints.
TypeScript
4
star
92

pusher_tutorial_realtimeresults

JavaScript
3
star
93

pusher-js-diagnostics

JavaScript
3
star
94

react-rest-api-tutorial

Accompanying tutorial for consuming RESTful APIs in React
CSS
3
star
95

testing

Configuration for Pusher's Open Source Prow instance
Go
3
star
96

feeds-server-node

The server Node SDK for Pusher Feeds
JavaScript
3
star
97

spacegame_example

Simple example of a space game using node.js and Pusher
JavaScript
3
star
98

chatkit-quickstart-swift

A project to get started with Chatkit.
Swift
2
star
99

pusher-whos-in

Ruby
2
star
100

chatkit-android-public-demo

This will hold the demo app for chatkit android. Owner: @daniellevass
Kotlin
2
star