72888104f0bc26f7261541fb8384c54073be0285
[euphorik.git] / js / communication.js
1 // coding: utf-8
2 // Copyright 2008 Grégory Burri
3 //
4 // This file is part of Euphorik.
5 //
6 // Euphorik is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // Euphorik is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
18
19 // Regroupe la partie communication JSON client -> serveur de euphorik.
20 // Voir : http://dev.euphorik.ch/wiki/euk/Protocole
21
22 /**
23 * Les fonctions debutReq et finReq servent, par exemple, à afficher à l'utilisateur
24 * qu'une communication est en cours.
25 * @param funError un fonction executée lors d'un réponse 'error' de la part du serveur, peut être redéfinit pour une requête.
26 * @param funDebutReq fonction appelée au début d'une requête (facultatif)
27 * @param funFinReq fonction appelée à la fin d'une requête (facultatif)
28 */
29 euphorik.Communication = function(funError, funDebutReq, funFinReq) {
30 this.funError = funError;
31 this.funDebutReq = funDebutReq;
32 this.funFinReq = funFinReq;
33 };
34
35 /**
36 * Charge un fichier depuis une url et retourne son contenu.
37 */
38 euphorik.Communication.prototype.load = function(url) {
39 if (this.funDebutReq) {
40 this.funDebutReq();
41 }
42 var contenu = "";
43 $.ajax({async: false, url: url, success : function(page) { contenu += page; }});
44 if (this.funFinReq) {
45 this.funFinReq();
46 }
47 return contenu;
48 };
49
50 /**
51 * Effectue une requête JSON auprès du serveur.
52 * @param action une chaine spécifiant l'action, par exemple "put_message"
53 * @param json les données à envoyer associé à l'action, par exemple {"cookie" : "LKJDLAKSJBFLKASN", "nick" : "Paul", "content" : "Bonjour", "answer_to" : [] }
54 * @param funOk la fonction exécuté après réception des données du serveur
55 * @param funError la fonction exécuté si une erreur arrive (facultatif)
56 * @param asynchrone true pour une communication asychrone (facultatif, truepar défaut)
57 * @param paramsSupp un objet contenant des paramètres supplémentaire pour la fonction ajax de jQuery (facultatif)
58 */
59 euphorik.Communication.prototype.requete = function(action, json, funOk, funError, asynchrone, paramsSupp) {
60 var thisCommunication = this;
61 if (asynchrone === undefined) {
62 asynchrone = true; // asynchrone par défaut
63 }
64
65 var mess = this.getBase(action);
66 objectEach(json, function(nom, val) {
67 mess[nom] = val;
68 });
69
70 if (this.funDebutReq) {
71 this.funDebutReq();
72 }
73
74 paramsAjax = {
75 async: asynchrone,
76 type: "POST",
77 url: "request",
78 dataType: "json",
79 data: { action : JSON.stringify(mess) },
80 success:
81 function(data) {
82 if (thisCommunication.funFinReq) {
83 thisCommunication.funFinReq();
84 }
85 if (data.reply === "error") {
86 if (funError) {
87 funError(data);
88 } else if (thisCommunication.funError) {
89 thisCommunication.funError(data);
90 }
91 } else if (funOk) {
92 funOk(data);
93 }
94 },
95 error:
96 function(data) {
97 if (thisCommunication.funFinReq) {
98 thisCommunication.funFinReq();
99 }
100 }
101 };
102
103 if (paramsSupp) {
104 objectEach(paramsSupp, function(nom, val) {
105 paramsAjax[nom] = val;
106 });
107 }
108
109 jQuery.ajax(paramsAjax);
110 };
111
112 euphorik.Communication.prototype.createCometConnection = function(name) {
113 return new Comet(name, this.getBase);
114 };
115
116 euphorik.Communication.prototype.getBase = function(action) {
117 return {
118 "header" : { "action" : action, "version" : euphorik.conf.versionProtocole }
119 };
120 };
121
122 /**
123 * Permet de gérer les événements (push serveur).
124 * Principe de fonctionnement :
125 * - La page courante créer un objet euphorik.Comet en indiquant le nom de la page et la version du protocole.
126 * - La page courante attend un événement en appelant 'waitEvent' (non-bloquant) et en donnant deux fonctions :
127 * - 'funSend' une fonction qui renvoie l'objet à envoyer avant l'attente, par exemple {"dernierMess" : 23}
128 * ("header" et "page" sont automatiquement ajoutés à l'objet)
129 * - 'funsReceive' un ensemble de fonctions à appeler en fonction du "reply" du serveur, par exemple {"set_nom" : function(data) { print("ok : " + data.nom); } }
130 *
131 * l'information envoyée est sous la forme :
132 * {
133 * "header" : {"action" : "wait_event", "version" : <v> },
134 * "page" : <page>
135 * [..]
136 * }
137 * l'information reçue est sous la forme :
138 * {
139 * "reply" : <reply>
140 * [..]
141 * }
142 * <reply> et <page> sont de type chaine
143 *
144 * @page [string] la page courante pour laquelle on écoute des événements (un string)
145 * @util [int] la version
146 */
147 Comet = function(page, getBase) {
148 this.page = page;
149 this.getBase = getBase;
150
151 // l'objet JSONHttpRequest représentant la connexion d'attente
152 this.attenteCourante = undefined;
153
154 // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence...
155 this.stop = false;
156 };
157
158 /**
159 * Arrête l'attente courante s'il y en a une.
160 */
161 Comet.prototype.stopAttenteCourante = function() {
162 this.stop = true;
163
164 if (this.attenteCourante) {
165 this.attenteCourante.abort();
166 }
167 };
168
169 /**
170 * Attend un événement lié à la page. Non-bloquant.
171 * @funSend une fonction renvoyant les données json à envoyer
172 * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply"
173 * les fonctions acceptent un paramètre correspondant au données reçues.
174 * exemple : {"new_message" : function(data){ ... }}
175 */
176 Comet.prototype.waitEvent = function(funSend, funsReceive) {
177 this.stopAttenteCourante();
178 this.stop = false;
179 var thisComet = this;
180
181 // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole)
182 // TODO : ya pas mieux ?
183 var dataToSend = this.getBase("wait_event")
184 dataToSend["page"] = this.page;
185
186 var poulpe = funSend();
187 objectEach(poulpe, function(k, v) {
188 dataToSend[k] = v;
189 });
190
191 this.attenteCourante = jQuery.ajax({
192 type: "POST",
193 url: "request",
194 dataType: "json",
195 // TODO : doit disparaitre
196 timeout: 180000, // timeout de 3min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76
197 data: { action : JSON.stringify(dataToSend) },
198 success:
199 function(data) {
200 funsReceive[data.reply](data);
201
202 // rappel de la fonction dans 100 ms
203 setTimeout(function(){ thisComet.waitEvent2(funSend, funsReceive); }, 100);
204 },
205 error:
206 function(XMLHttpRequest, textStatus, errorThrown) {
207 ;; console.log("Connexion perdue dans Comet.prototype.waitEvent() : \n" + textStatus);
208 setTimeout(function(){ thisComet.waitEvent2(funSend, funsReceive); }, 1000);
209 }
210 });
211 };
212
213 /**
214 * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler.
215 */
216 Comet.prototype.waitEvent2 = function(funSend, funsReceive) {
217 if (this.stop) {
218 return;
219 }
220 this.waitEvent(funSend, funsReceive);
221 };