diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index b59c058..017b56b 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -79,14 +79,8 @@ Client.prototype = {
         // The second argument, recover, means _observeChannels will be run
         // for any existing channel as well.
         let dbus = Tp.DBusDaemon.dup();
-        this._observer = Tp.SimpleObserver.new(dbus, true, 'GnomeShell', true,
-                                              Lang.bind(this, this._observeChannels));
-
-        // We only care about single-user text-based chats
-        let props = {};
-        props[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT;
-        props[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.HandleType.CONTACT;
-        this._observer.add_observer_filter(props);
+        this._observer = Shell.TpClient.new(dbus,
+            Lang.bind(this, this._observeChannels));
 
         try {
             this._observer.register();
diff --git a/src/Makefile.am b/src/Makefile.am
index fa79fcc..8aa15d3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -98,6 +98,7 @@ shell_public_headers_h =		\
 	shell-perf-log.h		\
 	shell-slicer.h			\
 	shell-stack.h			\
+	shell-tp-client.h			\
 	shell-tray-icon.h		\
 	shell-tray-manager.h		\
 	shell-util.h			\
@@ -132,6 +133,7 @@ libgnome_shell_la_SOURCES =		\
 	shell-polkit-authentication-agent.c	\
 	shell-slicer.c			\
 	shell-stack.c			\
+	shell-tp-client.c			\
 	shell-tray-icon.c		\
 	shell-tray-manager.c		\
 	shell-util.c			\
diff --git a/src/shell-global.c b/src/shell-global.c
index 2123e8a..8346cc7 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -2050,66 +2050,6 @@ gboolean _shell_global_check_xdnd_event (ShellGlobal  *global,
 }
 
 /**
- * ShellGetTpContactCb:
- * @connection: The connection
- * @contacts: (element-type TelepathyGLib.Contact): List of contacts
- * @failed: Array of failed contacts
- */
-
-static void
-shell_global_get_tp_contacts_cb (TpConnection *self,
-                                 guint n_contacts,
-                                 TpContact * const *contacts,
-                                 guint n_failed,
-                                 const TpHandle *failed,
-                                 const GError *error,
-                                 gpointer user_data,
-                                 GObject *weak_object)
-{
-  int i;
-  GList *contact_list = NULL;
-  for (i = 0; i < n_contacts; i++) {
-      contact_list = g_list_append(contact_list, contacts[i]);
-  }
-
-  TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1);
-  memcpy(failed_list, failed, n_failed);
-
-  ((ShellGetTpContactCb)user_data)(self, contact_list, failed_list);
-}
-
-
-/**
- * shell_get_tp_contacts:
- * @self: A connection, which must be ready
- * @n_handles: Number of handles in handles
- * @handles: (array length=n_handles) (element-type uint): Array of handles
- * @n_features: Number of features in features
- * @features: (array length=n_features) (allow-none) (element-type uint):
- *  Array of features
- * @callback: (scope async): User callback to run when the contacts are ready
- *
- * Wrap tp_connection_get_contacts_by_handle so we can transform the array
- * into a null-terminated one, which gjs can handle.
- * We send the original callback to tp_connection_get_contacts_by_handle as
- * user_data, and we have our own function as callback, which does the
- * transforming.
- */
-void
-shell_get_tp_contacts (TpConnection *self,
-                       guint n_handles,
-                       const TpHandle *handles,
-                       guint n_features,
-                       const TpContactFeature *features,
-                       ShellGetTpContactCb callback)
-{
-  tp_connection_get_contacts_by_handle(self, n_handles, handles,
-                                       n_features, features,
-                                       shell_global_get_tp_contacts_cb,
-                                       callback, NULL, NULL);
-}
-
-/**
  * shell_global_launch_calendar_server:
  * @global: The #ShellGlobal.
  *
@@ -2162,70 +2102,3 @@ shell_global_launch_calendar_server (ShellGlobal *global)
 
   g_free (calendar_server_exe);
 }
-
-static void
-shell_global_get_self_contact_features_cb (TpConnection *connection,
-                                           guint n_contacts,
-                                           TpContact * const *contacts,
-                                           const GError *error,
-                                           gpointer user_data,
-                                           GObject *weak_object)
-{
-  if (error != NULL) {
-    g_print ("Failed to upgrade self contact: %s", error->message);
-    return;
-  }
-  ((ShellGetSelfContactFeaturesCb)user_data)(connection, *contacts);
-}
-
-/**
- * shell_get_self_contact_features:
- * @self: A connection, which must be ready
- * @n_features: Number of features in features
- * @features: (array length=n_features) (allow-none) (element-type uint):
- *  Array of features
- * @callback: (scope async): User callback to run when the contact is ready
- * 
- * Wrap tp_connection_upgrade_contacts due to the lack of support for
- * proper arrays arguments in GJS.
- */
-void
-shell_get_self_contact_features (TpConnection *self,
-                                 guint n_features,
-                                 const TpContactFeature *features,
-                                 ShellGetSelfContactFeaturesCb callback)
-{
-  TpContact *self_contact = tp_connection_get_self_contact (self);
-
-  tp_connection_upgrade_contacts (self, 1, &self_contact,
-                                  n_features, features,
-                                  shell_global_get_self_contact_features_cb,
-                                  callback, NULL, NULL);
-}
-
-/**
- * shell_get_contact_events:
- * @log_manager: A #TplLogManager
- * @account: A #TpAccount
- * @entity: A #TplEntity
- * @num_events: The number of events to retrieve
- * @callback: (scope async): User callback to run when the contact is ready
- * 
- * Wrap tpl_log_manager_get_filtered_events_async because gjs cannot support
- * multiple callbacks in the same function call.
- */
-void
-shell_get_contact_events (TplLogManager *log_manager,
-                          TpAccount *account,
-                          TplEntity *entity,
-                          guint num_events,
-                          GAsyncReadyCallback callback)
-{
-  tpl_log_manager_get_filtered_events_async (log_manager,
-                                             account,
-                                             entity,
-                                             TPL_EVENT_MASK_TEXT,
-                                             num_events,
-                                             NULL, NULL,
-                                             callback, NULL);
-}
diff --git a/src/shell-global.h b/src/shell-global.h
index 6e16abe..b816df7 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -7,8 +7,6 @@
 #include <gdk-pixbuf/gdk-pixbuf.h>
 #include <gtk/gtk.h>
 #include <meta/meta-plugin.h>
-#include <telepathy-glib/telepathy-glib.h>
-#include <telepathy-logger/telepathy-logger.h>
 
 G_BEGIN_DECLS
 
@@ -158,33 +156,8 @@ void shell_global_notify_error (ShellGlobal  *global,
 
 void shell_global_init_xdnd (ShellGlobal *global);
 
-typedef void (*ShellGetTpContactCb) (TpConnection *connection,
-                                     GList *contacts,
-                                     TpHandle *failed);
-
-void shell_get_tp_contacts (TpConnection *self,
-                            guint n_handles,
-                            const TpHandle *handles,
-                            guint n_features,
-                            const TpContactFeature *features,
-                            ShellGetTpContactCb callback);
-
 void shell_global_launch_calendar_server (ShellGlobal *global);
 
-typedef void (*ShellGetSelfContactFeaturesCb) (TpConnection *connection,
-                                               TpContact *contact);
-
-void shell_get_self_contact_features (TpConnection *self,
-                                      guint n_features,
-                                      const TpContactFeature *features,
-                                      ShellGetSelfContactFeaturesCb callback);
-
-void shell_get_contact_events (TplLogManager *log_manager,
-                               TpAccount *account,
-                               TplEntity *entity,
-                               guint num_events,
-                               GAsyncReadyCallback callback);
-
 G_END_DECLS
 
 #endif /* __SHELL_GLOBAL_H__ */
diff --git a/src/shell-tp-client.c b/src/shell-tp-client.c
new file mode 100644
index 0000000..8bc7a90
--- /dev/null
+++ b/src/shell-tp-client.c
@@ -0,0 +1,321 @@
+#include "shell-tp-client.h"
+
+#include <string.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+
+G_DEFINE_TYPE(ShellTpClient, shell_tp_client, TP_TYPE_BASE_CLIENT)
+
+enum
+{
+  PROP_OBSERVE_IMPL = 1,
+  PROP_USER_DATA,
+  PROP_DESTROY,
+  N_PROPS
+};
+
+struct _ShellTpClientPrivate
+{
+  ShellTpClientObserveChannelsImpl observe_impl;
+  gpointer user_data;
+  GDestroyNotify destroy;
+};
+
+/**
+ * ShellTpClientObserveChannelsImpl:
+ * @client: a #ShellTpClient instance
+ * @account: a #TpAccount having %TP_ACCOUNT_FEATURE_CORE prepared if possible
+ * @connection: a #TpConnection having %TP_CONNECTION_FEATURE_CORE prepared
+ * if possible
+ * @channels: (element-type TelepathyGLib.Channel): a #GList of #TpChannel,
+ *  all having %TP_CHANNEL_FEATURE_CORE prepared if possible
+ * @dispatch_operation: (allow-none): a #TpChannelDispatchOperation or %NULL;
+ *  the dispatch_operation is not guaranteed to be prepared
+ * @requests: (element-type TelepathyGLib.ChannelRequest): a #GList of
+ *  #TpChannelRequest, all having their object-path defined but are not
+ *  guaranteed to be prepared.
+ * @context: a #TpObserveChannelsContext representing the context of this
+ *  D-Bus call
+ *
+ * Signature of the implementation of the ObserveChannels method.
+ */
+
+static void
+shell_tp_client_init (ShellTpClient *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_TP_CLIENT,
+      ShellTpClientPrivate);
+
+  /* Observer */
+
+  /* We only care about single-user text-based chats */
+  tp_base_client_take_observer_filter (TP_BASE_CLIENT (self),
+      tp_asv_new (
+        TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+          TP_IFACE_CHANNEL_TYPE_TEXT,
+        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+          TP_HANDLE_TYPE_CONTACT,
+        NULL));
+}
+
+static void
+observe_channels (
+    TpBaseClient *client,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    TpChannelDispatchOperation *dispatch_operation,
+    GList *requests,
+    TpObserveChannelsContext *context)
+{
+  ShellTpClient *self = (ShellTpClient *) client;
+
+  self->priv->observe_impl (self, account, connection, channels,
+      dispatch_operation, requests, context, self->priv->user_data);
+}
+
+static void
+shell_tp_client_set_property (GObject *object,
+    guint property_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  ShellTpClient *self = SHELL_TP_CLIENT (object);
+
+  switch (property_id)
+    {
+      case PROP_OBSERVE_IMPL:
+        self->priv->observe_impl = g_value_get_pointer (value);
+        break;
+
+      case PROP_USER_DATA:
+        self->priv->user_data = g_value_get_pointer (value);
+        break;
+
+      case PROP_DESTROY:
+        self->priv->destroy = g_value_get_pointer (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+  }
+}
+
+static void
+shell_tp_client_constructed (GObject *object)
+{
+  ShellTpClient *self = SHELL_TP_CLIENT (object);
+  void (*chain_up) (GObject *) =
+    ((GObjectClass *) shell_tp_client_parent_class)->constructed;
+
+  if (chain_up != NULL)
+    chain_up (object);
+
+  g_assert (self->priv->observe_impl != NULL);
+}
+
+static void
+shell_tp_client_dispose (GObject *object)
+{
+  ShellTpClient *self = SHELL_TP_CLIENT (object);
+  void (*dispose) (GObject *) =
+    G_OBJECT_CLASS (shell_tp_client_parent_class)->dispose;
+
+  if (self->priv->destroy != NULL)
+    {
+      self->priv->destroy (self->priv->user_data);
+      self->priv->destroy = NULL;
+    }
+
+  if (dispose != NULL)
+    dispose (object);
+}
+
+static void
+shell_tp_client_class_init (ShellTpClientClass *cls)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (cls);
+  TpBaseClientClass *base_clt_cls = TP_BASE_CLIENT_CLASS (cls);
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (cls, sizeof (ShellTpClientPrivate));
+
+  object_class->set_property = shell_tp_client_set_property;
+  object_class->constructed = shell_tp_client_constructed;
+  object_class->dispose = shell_tp_client_dispose;
+
+  base_clt_cls->observe_channels = observe_channels;
+
+  param_spec = g_param_spec_pointer ("observe-implementation",
+      "Observe implementation",
+      "Function called when ObserveChannels is called",
+      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_OBSERVE_IMPL, param_spec);
+
+  param_spec = g_param_spec_pointer ("user-data", "user data",
+      "pointer passed as user-data when ObserveChannels is called",
+      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_USER_DATA,
+      param_spec);
+
+  param_spec = g_param_spec_pointer ("destroy", "destroy",
+      "function called to destroy the user-data when destroying the client",
+      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_DESTROY,
+      param_spec);
+}
+
+/**
+ * shell_tp_client_new:
+ * @dbus: a #TpDBusDaemon object, may not be %NULL
+ * @observe_impl: the function called when ObserveChannels is called
+ * @user_data: arbitrary user-supplied data passed to @observe_impl
+ * @destroy: called with @user_data as argument, when the #ShellTpClient
+ * is destroyed
+ *
+ * Returns: a new #ShellTpClient
+ */
+ShellTpClient *
+shell_tp_client_new (TpDBusDaemon *dbus,
+    ShellTpClientObserveChannelsImpl observe_impl,
+    gpointer user_data,
+    GDestroyNotify destroy)
+{
+  return g_object_new (SHELL_TYPE_TP_CLIENT,
+      "dbus-daemon", dbus,
+      "name", "GnomeShell",
+      "uniquify-name", TRUE,
+      "observe-implementation", observe_impl,
+      "user-data", user_data,
+      "destroy", destroy,
+      NULL);
+}
+
+/* Telepathy utility functions */
+
+/**
+ * ShellGetTpContactCb:
+ * @connection: The connection
+ * @contacts: (element-type TelepathyGLib.Contact): List of contacts
+ * @failed: Array of failed contacts
+ */
+
+static void
+shell_global_get_tp_contacts_cb (TpConnection *self,
+                                 guint n_contacts,
+                                 TpContact * const *contacts,
+                                 guint n_failed,
+                                 const TpHandle *failed,
+                                 const GError *error,
+                                 gpointer user_data,
+                                 GObject *weak_object)
+{
+  int i;
+  GList *contact_list = NULL;
+  for (i = 0; i < n_contacts; i++) {
+      contact_list = g_list_append(contact_list, contacts[i]);
+  }
+
+  TpHandle *failed_list = g_new0 (TpHandle, n_failed + 1);
+  memcpy(failed_list, failed, n_failed);
+
+  ((ShellGetTpContactCb)user_data)(self, contact_list, failed_list);
+}
+
+/**
+ * shell_get_tp_contacts:
+ * @self: A connection, which must be ready
+ * @n_handles: Number of handles in handles
+ * @handles: (array length=n_handles) (element-type uint): Array of handles
+ * @n_features: Number of features in features
+ * @features: (array length=n_features) (allow-none) (element-type uint):
+ *  Array of features
+ * @callback: (scope async): User callback to run when the contacts are ready
+ *
+ * Wrap tp_connection_get_contacts_by_handle so we can transform the array
+ * into a null-terminated one, which gjs can handle.
+ * We send the original callback to tp_connection_get_contacts_by_handle as
+ * user_data, and we have our own function as callback, which does the
+ * transforming.
+ */
+void
+shell_get_tp_contacts (TpConnection *self,
+                       guint n_handles,
+                       const TpHandle *handles,
+                       guint n_features,
+                       const TpContactFeature *features,
+                       ShellGetTpContactCb callback)
+{
+  tp_connection_get_contacts_by_handle(self, n_handles, handles,
+                                       n_features, features,
+                                       shell_global_get_tp_contacts_cb,
+                                       callback, NULL, NULL);
+}
+
+static void
+shell_global_get_self_contact_features_cb (TpConnection *connection,
+                                           guint n_contacts,
+                                           TpContact * const *contacts,
+                                           const GError *error,
+                                           gpointer user_data,
+                                           GObject *weak_object)
+{
+  if (error != NULL) {
+    g_print ("Failed to upgrade self contact: %s", error->message);
+    return;
+  }
+  ((ShellGetSelfContactFeaturesCb)user_data)(connection, *contacts);
+}
+
+/**
+ * shell_get_self_contact_features:
+ * @self: A connection, which must be ready
+ * @n_features: Number of features in features
+ * @features: (array length=n_features) (allow-none) (element-type uint):
+ *  Array of features
+ * @callback: (scope async): User callback to run when the contact is ready
+ *
+ * Wrap tp_connection_upgrade_contacts due to the lack of support for
+ * proper arrays arguments in GJS.
+ */
+void
+shell_get_self_contact_features (TpConnection *self,
+                                 guint n_features,
+                                 const TpContactFeature *features,
+                                 ShellGetSelfContactFeaturesCb callback)
+{
+  TpContact *self_contact = tp_connection_get_self_contact (self);
+
+  tp_connection_upgrade_contacts (self, 1, &self_contact,
+                                  n_features, features,
+                                  shell_global_get_self_contact_features_cb,
+                                  callback, NULL, NULL);
+}
+
+/**
+ * shell_get_contact_events:
+ * @log_manager: A #TplLogManager
+ * @account: A #TpAccount
+ * @entity: A #TplEntity
+ * @num_events: The number of events to retrieve
+ * @callback: (scope async): User callback to run when the contact is ready
+ *
+ * Wrap tpl_log_manager_get_filtered_events_async because gjs cannot support
+ * multiple callbacks in the same function call.
+ */
+void
+shell_get_contact_events (TplLogManager *log_manager,
+                          TpAccount *account,
+                          TplEntity *entity,
+                          guint num_events,
+                          GAsyncReadyCallback callback)
+{
+  tpl_log_manager_get_filtered_events_async (log_manager,
+                                             account,
+                                             entity,
+                                             TPL_EVENT_MASK_TEXT,
+                                             num_events,
+                                             NULL, NULL,
+                                             callback, NULL);
+}
diff --git a/src/shell-tp-client.h b/src/shell-tp-client.h
new file mode 100644
index 0000000..854a49a
--- /dev/null
+++ b/src/shell-tp-client.h
@@ -0,0 +1,87 @@
+#ifndef __SHELL_TP_CLIENT_H__
+#define __SHELL_TP_CLIENT_H__
+
+#include <dbus/dbus-glib.h>
+#include <glib-object.h>
+
+#include <telepathy-glib/telepathy-glib.h>
+#include <telepathy-logger/telepathy-logger.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ShellTpClient ShellTpClient;
+typedef struct _ShellTpClientClass ShellTpClientClass;
+typedef struct _ShellTpClientPrivate ShellTpClientPrivate;
+
+struct _ShellTpClientClass {
+    /*<private>*/
+    TpBaseClientClass parent_class;
+};
+
+struct _ShellTpClient {
+    /*<private>*/
+    TpBaseClient parent;
+    ShellTpClientPrivate *priv;
+};
+
+GType shell_tp_client_get_type (void);
+
+#define SHELL_TYPE_TP_CLIENT \
+  (shell_tp_client_get_type ())
+#define SHELL_TP_CLIENT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_TP_CLIENT, \
+                               ShellTpClient))
+#define SHELL_TP_CLIENT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_TP_CLIENT, \
+                            ShellTpClientClass))
+#define TP_IS_TP_CLIENT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_TP_CLIENT))
+#define TP_IS_TP_CLIENT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_TP_CLIENT))
+#define SHELL_TP_CLIENT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_TP_CLIENT, \
+                              ShellTpClientClass))
+
+typedef void (*ShellTpClientObserveChannelsImpl) (
+    ShellTpClient *client,
+    TpAccount *account,
+    TpConnection *connection,
+    GList *channels,
+    TpChannelDispatchOperation *dispatch_operation,
+    GList *requests,
+    TpObserveChannelsContext *context,
+    gpointer user_data);
+
+ShellTpClient * shell_tp_client_new (TpDBusDaemon *dbus,
+    ShellTpClientObserveChannelsImpl observe_impl,
+    gpointer user_data,
+    GDestroyNotify destroy);
+
+/* Telepathy utility functions */
+typedef void (*ShellGetTpContactCb) (TpConnection *connection,
+                                     GList *contacts,
+                                     TpHandle *failed);
+
+void shell_get_tp_contacts (TpConnection *self,
+                            guint n_handles,
+                            const TpHandle *handles,
+                            guint n_features,
+                            const TpContactFeature *features,
+                            ShellGetTpContactCb callback);
+
+typedef void (*ShellGetSelfContactFeaturesCb) (TpConnection *connection,
+                                               TpContact *contact);
+
+void shell_get_self_contact_features (TpConnection *self,
+                                      guint n_features,
+                                      const TpContactFeature *features,
+                                      ShellGetSelfContactFeaturesCb callback);
+
+void shell_get_contact_events (TplLogManager *log_manager,
+                               TpAccount *account,
+                               TplEntity *entity,
+                               guint num_events,
+                               GAsyncReadyCallback callback);
+
+G_END_DECLS
+#endif /* __SHELL_TP_CLIENT_H__ */
