Mit Pusher in Echtzeit arbeiten

Möchten Sie Ihre Webanwendungen in Echtzeit aufpeppen - möchten Sie jedoch keine neuen Infrastrukturen erstellen, nur um Web Sockets zum Laufen zu bringen? In diesem Artikel werden wir die Verwendung und Implementierung untersuchen Pusher, ein von WebSocket betriebener HTML5-Echtzeitnachrichtendienst für Ihre Anwendungen.


Einführung

Was sind WebSockets??

Gemäß der WebSocket-Wikipedia-Seite ist WebSocket eine Technologie, die bidirektionale Vollduplex-Kommunikationskanäle über einen einzigen TCP-Socket bereitstellt.

In der Laiensprache ermöglicht WebSockets, dass ein Client und ein Server in beide Richtungen kommunizieren können. Ein Server kann Nachrichten an den Client senden und umgekehrt.

Wie ist das für meine Webanwendung relevant??

Im Laufe der Jahre war der Datenablauf bei Webanwendungen immer ein Problem, insbesondere bei solchen, bei denen mehrere Personen angemeldet sind und an denselben Aufgaben arbeiten. Beispielsweise erstellen Benutzer in einer Projektverwaltungsanwendung manchmal Aufgaben, die von ihren Teammitgliedern gleichzeitig erstellt werden. Bei WebSockets kann dies durch Zulassen des Servers gemindert werden drücken Benachrichtigungen an alle verbundenen Parteien, sodass Browser neue Daten in Echtzeit empfangen können. Bevor Sie ein Duplikat einer Aufgabe erstellen, sehen Sie, dass bereits eine andere Person es erstellt hat.

Was ist Schieber??

Pusher ist eine gehostete API, mit der skalierbare Echtzeitfunktionen über WebSockets schnell, einfach und sicher zu Web- und mobilen Apps hinzugefügt werden können.

Im Wesentlichen kapselt Pusher die WebSockets-Implementierung, die Funktionalität, das Debugging und das Hosting für Sie.
Anstatt Ihren eigenen WebSockets-Server betreiben zu müssen, können Sie den gesamten Prozess auf die Server von Pusher übertragen, wodurch Sie Zeit und Geld sparen.

Pusher ist eine gehostete API, mit der skalierbare Echtzeitfunktionen über WebSockets schnell, einfach und sicher zu Web- und mobilen Apps hinzugefügt werden können.

Damit Pusher funktioniert, benötigen Sie sowohl eine Client-Bibliothek als auch eine Publisher-Bibliothek. Client-Bibliotheken werden mit dem Client verwendet, der mit Ihrer Anwendung verbunden ist. Dies kann ein Browser (über JavaScript), eine iPhone-App (über Objective-C) oder eine Flash-App (über ActionScript) sein. Publisher-Bibliotheken werden auf Ihrem Server verwendet, um Ereignisse an Ihre Clients zu senden.

Derzeit verfügt Pusher über Client-Bibliotheken für JavaScript, Objective-C, ActionScript, .NET und Silverlight, Ruby und Arduino. Es verfügt über Herausgeberbibliotheken für Node.js, Java, Groovy, Grails, Clojure, Python, VB.NET, C #, PHP, Ruby, Perl und ColdFusion.

In diesem Tutorial verwenden wir die JavaScript-Client-Bibliothek und die PHP-Publisher-Bibliothek. Die Implementierung sollte nicht zu unterschiedlich sein, wenn Sie eine andere Programmiersprache verwenden.

Ich möchte ein Live-Chat-Widget erstellen, mit dem Benutzer auf einer Website in Echtzeit chatten können. Lassen Sie uns in diesem Sinne fortfahren.


Pusher einrichten

Schritt 1: Registrieren Sie sich für einen kostenlosen Pusher-Entwicklerkonto

Um zu beginnen, gehen Sie auf die Pusher-Website und registrieren Sie sich für Ihr Konto. Sie bieten ein kostenloses Konto für Sandbox-Planbenutzer, einschließlich 20 Verbindungen und 100.000 Nachrichten pro Tag. Wenn Sie bereit sind, können Sie jederzeit ein Upgrade auf einen bezahlten Tarif durchführen. Da wir ihn jedoch nur für unsere Beispielanwendung verwenden, ist ein kostenloser Sandbox-Plan die Lösung!


Pusher-Registrierung

Klicken Sie auf der Site auf die Schaltfläche Anmelden Schaltfläche, die Sie in der oberen rechten Ecke finden, und geben Sie die erforderlichen Details ein. Wenn Sie fertig sind, klicken Sie auf Anmelden Taste erneut, um die Registrierung abzuschließen.


Schritt 2: Melden Sie sich zum ersten Mal an

Nach der Registrierung werden Sie zu Ihrem Telefon weitergeleitet Pusher-Verwaltungsseite. Hier können Sie alle Ihre Pusher-Anwendungen verwalten. Ein einzelnes Konto kann mehrere Anwendungen hosten.


Pusher-Verwaltungsseite

Oben haben Sie Ihre Navigationsleiste, in der Sie die folgenden Abschnitte finden:

  • Instrumententafel - Hier sehen Sie die Statistiken Ihrer Pusher-Anwendung. Du kannst das ... sehen Nachrichtenrate (Anzahl der gesendeten Nachrichten pro Minute), Verbindungen (Anzahl der offenen Verbindungen zu einer bestimmten Zeit) und Mitteilungen (Gesamtnachrichten, die Ihre Anwendung pro Tag sendet).
  • Bearbeiten - Hier können Sie die aktuelle Anwendung umbenennen und auswählen, ob Sie die SSL-Verschlüsselung verwenden möchten.
  • API-Zugriff - Hier finden Sie Ihre Bewerbung API-Anmeldeinformationen, was wir später benötigen werden.
  • Debuggen - Hier werden alle Ereignisse und Nachrichten angezeigt, die von Ihrer Pusher-Anwendung gesendet wurden, sowie wenn Clients verbunden oder getrennt werden. Dies ist äußerst hilfreich bei der Entwicklung Ihrer Web-App, da Sie hier genau sehen können, was Pusher sendet und empfängt und wer online ist, um sie zu erhalten.
  • Ereignisersteller - Dies ist ein nützliches Werkzeug, um Testereignisse an Ihre verbundenen Clients zu senden - ohne dass Sie die Ereignisse selbst aus Ihrer Webanwendung auslösen müssen.

Sie können jetzt mit Pusher arbeiten!


Entwickeln mit Pusher

Schritt 1: Erstellen Sie HTML, CSS, JavaScript und PHP

Beginnen wir mit der Entwicklung unseres Live-Chat-Widget, indem Sie HTML erstellen. Was ich im Sinn habe, ist ein Widget, das am unteren Rand des Bildschirms mit einem "Wer ist online" angezeigt wird. Liste auf der Seite, wie IRC.

    
Einloggen?
Chatnachrichten gehen hier

Wer ist online (0)

  • Online-Benutzer gehen hier
Senden?

Einige CSS zur Gestaltung unseres HTML-Codes:

 #chat_widget_container padding: 20px 20px 5px 20px; Hintergrundfarbe: # F2F2F2; Grenze: 5px fest #AFAFAF; Rand unten: 0px; Breite: 333px; Schriftgröße: 11px; Schriftfamilie: "Lucida Grande", Arial, Helvetica, Serifenlos; Position: feststehend; unten: 0px; rechts: 20px #chat_widget_login width: 333px; Text ausrichten: Mitte; Höhe: 166px; margin-top: 80px #chat_widget_main_container display: none #chat_widget_messages_container float: left; Breite: 200px; Rand: 1px fest #DDD; Höhe: 200px; Überlauf: Auto; Polsterung: 5px; Hintergrundfarbe: #FFF; position: relative #chat_widget_messages overflow-x: hidden; Überlauf-y: Auto; Position: absolut; unten: 0px #chat_widget_online width: 100px; Höhe: 210px; Schwimmer: links; Auffüllen: 0px 10px; Rand: 1px fest #DDD; Rahmen links: 0px; Hintergrundfarbe: #FFF; overflow: auto; #chat_widget_online_list list-style: none; Auffüllen: 0px #chat_widget_online_list> li Rand links: 0px #chat_widget_input_container Rand oben: 10px; Textausrichtung: left #chat_widget_input width: 260px; Rand rechts: 10px; Rand: 1px fest #DDD; Auffüllen: 2px 5px #chat_widget_loader display: none #chat_widget_login_loader display: none .clear clear: both

Das kombinierte HTML und CSS sollte etwas wie folgt darstellen:


Demo-Login

Wir müssen eine Funktion erstellen, die ausgelöst wird, wenn Sie auf klicken Anmeldung und den eingegebenen Wert überprüfen, also machen wir das:

 $ ('# chat_widget_login_button'). click (function () $ (this) .hide (); // Den Anmeldebutton ausblenden $ ('# chat_widget_login_loader'). show (); // zeige den Loader-Gif-Benutzernamen = $ ('#chat_widget_username'). val (); // Abrufen des Benutzernamens Benutzername = Benutzername.Erstellplatz (/ [^ a-z0-9] / gi, "); // Filtern Sie es, wenn (Benutzername ==") / / Wenn das Feld leer ist, wird der Benutzer gewarnt ('Bitte geben Sie einen gültigen Benutzernamen (nur alphanumerisch))'); else // sonst, melden Sie sich bei unserem Benutzer über start_session.php an. ajaxCall ('start_session.php', username: username , function () // Wir sind angemeldet! ​​Was nun?););

Als Nächstes müssen wir den Server informieren, wenn wir angemeldet sind. Dazu erstellen wir eine start_session.php Datei, die den Benutzer im Wesentlichen anmeldet.

  wahr)); Ausfahrt(); ?>

Sie werden feststellen, dass ich eine erstellt habe AjaxCall Funktion, die im Grunde nur die jQuery $ .ajax-Funktion umgibt. Fügen Sie dies einfach vor der Zeile $ (document) .ready () hinzu.

 Funktion ajaxCall (ajax_url, ajax_data, successCallback) $ .ajax (Typ: "POST", URL: ajax_url, Datentyp: "json", Daten: ajax_data, time: 10, success: function (msg) if (msg.) success) successCallback (msg); else alert (msg.errormsg);, error: function (msg) ); 

Laden wir nun auch die Pusher-JavaScript-Bibliothek und jQuery. Platzieren Sie die folgenden Skriptverweise in der von Ihrem HTML:

  

Schritt 2: Notieren Sie sich Ihre API-Anmeldeinformationen

Erinnere dich an die API-Zugriff Seite von oben? Gehen Sie zurück und notieren Sie Ihre API-Anmeldeinformationen. Wir benötigen diese Werte, wenn wir die Client- und Publisher-Bibliothek einrichten.


Push-API-Anmeldeinformationen

Fühlen Sie sich frei, meine zu verwenden, aber ich empfehle Ihnen dringend, sich selbst zu besorgen, da ein kostenloses Konto begrenzt ist und Sie möglicherweise den Midstream abschneiden.

Schritt 3: Implementieren Sie den Pusher-Code

Bevor wir Pusher in unsere Anwendung implementieren, müssen wir einige Pusher-Begriffe verstehen:

  • Kanal - eine Möglichkeit, Datenströme innerhalb einer Anwendung zu unterscheiden. Eine Anwendung kann mehrere Kanäle haben, und ein Kanal kann mehrere Clients haben. Wir können dies mit einem Chatroom im IRC vergleichen - alle Nachrichten, die an einen bestimmten Chatroom gesendet werden, können von allen Personen eingesehen werden, die sich darin befinden.
  • Veranstaltungen - Dies ist vergleichbar mit dem Server, der Daten an den Client sendet, sodass Sie Nachrichten im Chatroom anzeigen können. Ereignisse werden von der Herausgeberbibliothek ausgelöst, und Kunden können diese Ereignisse abonnieren. In unserer Analogie gleicht das Abonnieren eines Ereignisses dem Zuhören, wenn sich Leute im Raum unterhalten und sich merken, was sie sagen.

Es gibt drei Arten von Kanälen:

  • Öffentliche Kanäle - Kanäle, die jeder abonnieren kann, sofern er den Namen des Kanals kennt.
  • Private Kanäle - Channels, die nur authentifizierte Benutzer abonnieren können.
  • Präsenzkanäle - ähnlich wie private Kanäle, ermöglichen es uns aber auch, andere verbundene Clients mit Informationen über die Verbindung des Clients zu benachrichtigen. Wir werden diesen Kanal in unserem Chat-Widget verwenden.

Präsenzkanäle sind etwas Besonderes, da sie uns ermöglichen, Informationen über Benutzer zu senden, wenn sie sich verbinden. Sie haben auch spezielle Ereignisse, die wir abonnieren können, um zu erfahren, wann sich ein Benutzer verbindet und abbricht. Präsenzkanäle sind ideal für sichere, private Kanäle, die wissen müssen, wann ein Benutzer ein- oder ausfährt.

Verbindung zum Pusher-Dienst herstellen

Beginnen wir damit, unseren Kunden mit dem Pusher-Service zu verbinden. Dazu müssen wir eine neue Instanz des Pusher-Objekts (aus der Bibliothek) erstellen und den Befehl aufrufen abonnieren Funktion. Fügen Sie den folgenden Code nach dem hinzu // Wir sind angemeldet! Was jetzt? Kommentar.

Das Abonnieren Funktion bewirkt im Wesentlichen, dass der Client dem Kanal beitritt. Innerhalb des Channels kann der Client Ereignisse empfangen, die in diesem Kanal stattfinden.

 Drücker = Neuer Drücker ('12c4f4771a7f75100398'); // APP KEY Pusher.channel_auth_endpoint = 'pusher_auth.php'; // überschreibe den channel_auth_endpoint nettuts_channel = pusher.subscribe ('presence-nettuts '); // Trete dem Präsenz-Nettuts-Kanal bei

Was ist ein ?channel_auth_endpoint??

Beim Abonnieren von a Gegenwart oder Privatgelände Channel müssen wir sicherstellen, dass der verbindende Benutzer auf den Channel zugreifen darf. Bevor der Client eine vollständige Verbindung mit ihm herstellen kann, ruft der Pusher-Client daher automatisch die in der URL definierte URL auf channel_auth_endpoint Variable und sendet Informationen über den Benutzer, der eine Verbindung herstellt. Dann durch channel_auth_endpoint, Wir können herausfinden, ob der verbindende Benutzer autorisiert ist.

Standardmäßig erfolgt dieser Aufruf an / drücker / auth, aber wir können es überschreiben, indem wir das setzen channel_auth_endpoint Variable.

Ein Unikat socket_id wird generiert und von Pusher an den Browser gesendet. Wenn versucht wird, einen Privat- oder Präsenzkanal zu abonnieren das socket_id und Kanal Name wird über eine AJAX POST-Anfrage an Ihre Anwendung gesendet Dadurch wird der Benutzer berechtigt, über Ihr vorhandenes Authentifizierungssystem auf den Kanal zuzugreifen. Bei Erfolg sendet Ihre Anwendung eine Autorisierungszeichenfolge an den Browser zurück, die mit Ihrem Pusher-Secret signiert ist. Diese wird über das WebSocket an Pusher gesendet, wodurch die Berechtigung (2) abgeschlossen wird, wenn die Berechtigungszeichenfolge übereinstimmt.

Zurück zu unserer Anwendung müssen wir unsere erstellen channel_auth_endpoint. Erstellen Sie eine Datei, die aufgerufen wird pusher_auth.php und platziere das hier:

 hasAccessTo ($ channel_name) == false) header ("true", 403); echo ("not authored"); exit (); * / $ pusher = neuer Pusher ('12c4f4771a7f75100398', // APP KEY '51399f661b4e0ff15af6 ', // APP SECRET' 8896 '// APP-ID); // Alle Daten, die Sie über die Person senden möchten, die $ present_data = array (' username '=> $ _SESSION [' username ']) abonniert; echo $ Pusher-> Präsenz_auth ($ channel_name, // der Name des Kanals, den der Benutzer $ socket_id abonniert, // die Socket-ID, die von der Pusher-Client-Bibliothek $ _SESSION ['userid'] empfangen wurde), // eine UNIQUE USER ID, die identifiziert der Benutzer $ present_data // die Daten zur Person); exit ();?>

Jetzt, da wir unsere verbindenden Benutzer authentifizieren können, müssen wir einige JavaScript-Funktionen an Pusher-Events binden, um zu zeigen, dass wir uns bereits angemeldet haben. Aktualisieren Sie den Code unter dem // Wir sind angemeldet! Was jetzt? Kommentar wie folgt:

 // Wir sind angemeldet! Was jetzt? Drücker = Neuer Drücker ('12c4f4771a7f75100398'); // APP KEY Pusher.channel_auth_endpoint = 'pusher_auth.php'; // überschreibe den channel_auth_endpoint nettuts_channel = pusher.subscribe ('presence-nettuts '); // dem Präsenz-Nettuts-Kanal beitreten pusher.connection.bind ('connected', function () // eine Funktion binden, nachdem wir eine Verbindung zu Pusher $ ('# chat_widget_login_loader'). hide (); // ausgeblendet haben loading gif $ ('# chat_widget_login_button'). show (); // Die Login-Schaltfläche erneut anzeigen $ ('# chat_widget_login'). hide (); // Den Anmeldebildschirm ausblenden $ ('# chat_widget_main_container'). show () // den Chat-Bildschirm anzeigen // Hier binden wir uns an das Ereignis pusher: subscription_succeeded, das aufgerufen wird, wenn Sie // einen Kanal erfolgreich abonniert haben nettuts_channel.bind ('pusher: subscription_succeeded', function (members) // this erstellt eine Liste aller Online-Clients und legt die Online-Liste html fest // Er aktualisiert auch die Online-Anzahl. var whosonline_html = "; members.each (Funktion (Member) whosonline_html + = '
  • '+ member.info.username +'
  • '; ); $ ('# chat_widget_online_list'). html (whosonline_html); updateOnlineCount (); ); // Hier binden wir uns an das Ereignis pusher: member_added, das uns sagt, wann immer // ein anderer Benutzer den Kanal nettuts_channel.bind ('pusher: member_added', function (member) erfolgreich abonniert) // an den neuen verbundenen Clientnamen anhängt die Online-Liste // und aktualisiert auch die Online-Zählung $ ('# chat_widget_online_list'). anfügen ('
  • '+ member.info.username +'
  • '); updateOnlineCount (); ); // Hier binden wir an pusher: member_removed -Ereignis, das uns mitteilt, wann // jemand den Kanal abmeldet oder die Verbindung trennt. nettuts_channel.bind ('pusher: member_removed', function (member) // Dadurch wird der Client von der Online-Liste entfernt und aktualisiert die Online-Zählung $ ('# chat_widget_member_' + member.id) .remove (); updateOnlineCount ();); );

    Denken Sie daran, das updateOnlineCount (); Funktion über der $ (Dokument) .ready () Linie:

     function updateOnlineCount () $ ('# chat_widget_counter'). html ($ ('. chat_widget_member'). length); 

    Eine Erklärung dessen, was wir gerade hinzugefügt haben

    Das pusher.connection.bind Mit dieser Funktion können wir eine Callback-Funktion binden, wenn sich der Verbindungsstatus der Pusher ändert. Es gibt viele mögliche Status wie z initialisiert, verbunden, nicht verfügbar, fehlgeschlagen und getrennt. Wir werden sie in diesem Tutorial nicht verwenden, aber Sie können mehr darüber in der Pusher-Dokumentation lesen.

    Das channel_name.bind Mit function können wir eine Funktion an ein bestimmtes Ereignis binden, das innerhalb des Kanals auftreten kann. Standardmäßig haben Präsenzkanäle eigene Ereignisse, an die wir Funktionen wie die binden können Pusher: subscription_succeeded Veranstaltung, die wir oben verwendet haben. Weitere Informationen dazu finden Sie in der Dokumentation zu Client Presence Events.

    Testen Sie jetzt die App und sehen Sie, was passiert. Öffnen Sie dazu zwei Registerkarten Ihrer App und melden Sie sich zweimal an. Sie sollten so etwas sehen:


    Erster Test

    Wenn Sie eine Registerkarte schließen, wird auch der zweite Client geschlossen, wodurch unsere ausgelöst wird Schieber: member_removed Ereignis und Entfernen des Clients aus der Online-Liste:


    Zweiter Test

    Jetzt können wir endlich die Kernfunktionalität unserer Anwendung - den Live-Chat - implementieren.

    Implementieren der Live-Chat-Funktion

    Beginnen wir damit, eine Funktion an das Submit-Ereignis unseres Chat-Formulars zu binden:

     $ ('# chat_widget_form'). submit (Funktion () var chat_widget_input = $ ('# chat_widget_input'), chat_widget_button = $ ('# chat_widget_button'), chat_widget_loader = $ ('# chat_widget_loader'), message = chat_widget_input.val (); // erhalte den Wert aus der Texteingabe chat_widget_button.hide (); // verstecke den Chat-Button chat_widget_loader.show (); // zeige das Chat-Ladeprogramm gif ajaxCall ('send_message.php', message: message , function (msg) // ajax-Aufruf an send_message.php durchführen chat_widget_input.val ("); // die Texteingabe löschen chat_widget_loader.hide (); // das Loader-Gif chat_widget_button.show (); // ausblenden die Chat-Schaltfläche newMessageCallback (msg.data); // Anzeige der Nachricht mit der Funktion newMessageCallback); false zurückgeben;);

    Das newMessageCallback Funktion:

     function newMessageCallback (data) if (has_chat == false) // Wenn der Benutzer noch keine Chat-Nachrichten im Div hat $ ('# chat_widget_messages'). html ("); // entfernt den Inhalt dh 'chat Nachrichten gehen hier 'has_chat = true; // und setzen sie so, dass sie nicht wieder in diese if-Anweisung gelangen $ (' # chat_widget_messages '). append (data.message +')
    ');

    Danach müssen wir erstellen send_message.php um unseren AJAX-Anruf von oben zu erhalten und die neue Nachricht Veranstaltung:

     <$_SESSION['username']> $ message "; // Auslösen des 'new_message'-Ereignisses in unserem Kanal,' präsenz-nettuts '$ pusher-> trigger (' präsenz-nettuts '), // der Kanal' new_message ', // das Ereignis-Array (' message '=> $ message) // die zu sendenden Daten); // Echo des Success-Arrays für den Ajax-Aufruf echo json_encode (Array (' message '=> $ message,' success '=> true)); exit () ;?>

    Sie fragen sich wahrscheinlich, warum wir das abstrahiert haben newMessageCallback in seine eigene Funktion. Nun, wir müssen es erneut anrufen, wenn wir eine erhalten neue Nachricht Veranstaltung von Pusher. Der folgende Code bindet eine Funktion an ein Ereignis, das aufgerufen wird neue Nachricht, Das wird jedes Mal ausgelöst, wenn ein Benutzer eine Nachricht sendet. Fügen Sie diesen Code nach dem hinzu nettuts_channel.bind ('pusher: member_removed') Codeblock:

     nettuts_channel.bind ('new_message', function (data) newMessageCallback (data););

    Das Daten Variable in der Bindungsfunktion oben sind die Daten, die der Server in sendet $ pusher-> trigger () Anruf, der die Nachrichtendaten enthalten soll.

    Testen

    Lass uns unsere App noch einmal mit ausprobieren zwei Browser, keine tabs. (Oder versuchen Sie es mit einem Freund, wenn Sie ihn irgendwo hochgeladen haben.)


    Hallo Freund!

    Herzliche Glückwünsche! Sie haben erfolgreich eine funktionierende Anwendung mit Pusher erstellt.


    Fazit

    Da hast du es, eine Arbeit Echtzeit Anwendung von Pusher angetrieben. Besuchen Sie die Live-Chat-Demo, die ich hier eingerichtet habe.

    Es gibt noch viel mehr, über das ich in diesem Lernprogramm nicht gesprochen habe, z. B. das Debuggen Ihrer Apps, das Ausschließen von Empfängern von Ereignissen und das Auslösen von clientseitigen Ereignissen. Sie können diese jedoch einfach durch Lesen der Pusher-Dokumentation erfahren. Sie können sogar die Präsentation von Websites und Anwendungen überprüfen, die Pusher verwenden, um in Echtzeit zu arbeiten.

    Dieses Tutorial verkratzt im Allgemeinen nur die Oberfläche von Pusher und WebSockets. Mit dieser Art von Technologie ist das, was Sie tun können, nur durch das begrenzt, was Sie sich vorstellen können.

    Haben Sie versucht, etwas mit Pusher zu erstellen, oder haben Sie vor, dies so bald zu tun? Lass es mich in den Kommentaren wissen!

    Hinweis: Pusher hat angefordert, dass wir die API-Anmeldeinformationen zurücksetzen, die vom Demo-Konto in diesem Lernprogramm verwendet werden, um vor Missbrauch zu schützen. Ich entschuldige mich bei euch Jungs und hoffentlich könnt ihr einfach eure eigenen bekommen :) Danke!