Saltar al contenido

Cómo construir un chat con firmas electrónicas en Pusher, HelloSign y Spring Boot

Crear el archivo src/main/resources/templates/chat.html con el siguiente contenido:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001<! DOCTYPE HTML><htmlxmlns:th="http://www.thymeleaf. org"³³³"head"³³³"title"³³³"NDA Chat</title"³³³"metahttp-equiv="Content-Type "content="text/html; charset=UTF-8"/³"link/³"³³³"link/³"³³³"head"³³³"body"³³³"div chatInfo.chatName}"||división:text="${sesión. chatInfo.chatDescription}"¡¡¡¡¡ChatInfo.chatDescription}">div;*div;*div;*div;*;*imgsrc="/img/loader.gif "*;*div;*div;*;*div;*;!  id}"div;div;www.inline="text "www. inline="text "www. inline="text"/i> [[${msg.user.name}]]www. inline="spanth:text="${msg. createdAtString}"${msg.messageFormatted}"}[spanth:text="${msg.messageFormatted}"${msg.messageFormatted}"}[spanth:spanth:divth:if="${session.chatInfo. idUser != msg.user.id}"-->;div;³³³"spanth:text="${msg.createdAtString}"-->;  ³³³"spanth:text="${msg.user. name}"${msg.messageFormatted}"³³³"/span>i³"/i³"³³³"/div³"³³³"divth:utext="${msg.messageFormatted}"³³³"³³³"/div³"³³³"/li³"³³³"/ul³"³³³"/div³"³³³";! -- end chat-history --;div>div>www.textareaname="message-to-send "placeholder="Escriba su mensaje "rows="3"</textareaaaaa;Enviar</aa.www.textareaname="${session.chatInfo.isUserChatOwner}"Solicitud NDA</a.www.textareaname="${session.chatInfo.isUserChatOwner}"Request NDA</a.www.textareaname;! -- end chat-message -- ¡¡¡¡fin de chat-message!!!! -- end container --N-Scripttype="text/x-handlebars-template"N->Li fa-circle online"N-{{{{{{nombre}}</span>N-Span my-message li alinear-derecha"³;span fa-circular me"³;/i>;div otro-mensaje flotar-derecha"³{{{{{msg}}</div³;³;/li>;/script="text/x-handlebars-template"³;div>
html

Revisemos este código. Esta parte imprimirá la información del chat de la sesión:

Cómo construir un chat con firmas electrónicas en Pusher, HelloSign y Spring Boot
Cómo construir un chat con firmas electrónicas en Pusher, HelloSign y Spring Boot
1234<div><divth:text="${session.chatInfo.chatName}"</div><divth:text="${session.chatInfo.chatDescription}"

html

Luego, imprimirá los mensajes enviados antes de que el usuario se suscriba al chat (diferenciando entre los mensajes enviados por el usuario local y los demás miembros del chat):

12345678910111213141516171819<lith:each="msg : ${messages}"<divth:if="${session.chatInfo.idUser == msg.user. id}"${{msg.nombre.usuario}]]};spanth:inline="text"³³³"i>/i> [[${msg.nombre.usuario}]]³³³"spanth:text="${msg.createdAtString}"³³³"/span messageFormatted}"³;</div³;³;divth:if="${session.chatInfo.idUser != msg.user.id}"³;div³;<spanth:text="${msg.createdAtString}"³;  <spanth:text="${msg. nombre.de.usuario}"${msg.messageFormatted}"

html

Si el usuario local es el propietario del chat, se muestra el botón Request NDA :

1&lt;ath:if="${session.chatInfo.isUserChatOwner}"<;Request NDA&lt;/a>;

html

Se definen las plantillas del manubrio para los mensajes:

12345678910111213141516171819202122232425262728293031&lt;scripttype="text/x-handlebars-template"³;li fa-circulo en línea scripttype="text/x-handlebars-template"³;li align-right"³;span fa-circle me"³;/i>;div other-message float-right"³{{{{{msg}}&lt;/div
html

Y las variables del servidor se imprimen usando la sintaxis de Thymeleaf para Javascript en línea:

1234&lt;script th_inline="javascript"&gt;varCHANNEL=/*[[${session.chatInfo.presenceChatName}]]*/$0027NA$0027;varPUSHER_KEY=/*[[${key}]]*/$0027NA$0027;&lt;/script&gt;

javascript

Para obtener la llave de empuje, modifiquemos el código del controlador. Pero primero, definamos un objeto de configuración que tome los valores de la API de Pusher de las variables de entorno (o línea de comandos):

123456789101112131415161718192021222324252627@ComponentpublicclassPusherSettings{ /** Pusher App ID */@Value("${pusher.appId}")privateString appId; /** Pusher Key */@Value("${pusher.key}")privateString key; /** Pusher Secret */@Value("${pusher. secret}")privateString secret; /** * Crea una nueva instancia del objeto Empujador para usar su API * * @return Una instancia del objeto Empujador */publicPushernewInstance(){returnnewPusher(appId, key, secret);}publicStringgetPusherKey(){return key;}}

java

Los valores de las propiedades pueden ser inyectados directamente en los frijoles usando la anotación @Valor y son accesibles después de que un frijol ha sido construido.

Ahora, en el ChatController, inyecta una instancia de este objeto:

12@AutocableadoprivadoPulsorPulsorPulsorPulsorPulsorPulsor
java

Modificar el método de chat para añadir la clave al objeto modelAndView:

12345678910@RequestMapping(method=RequestMethod.GET, value="/chat")publicModelAndViewchat(@SessionAttribute(GeneralConstants.ID_SESSION_CHAT_INFO)ChatForm chatInfo){... modelAndView.addObject("key", pusherSettings.getPusherKey());return modelAndView;}

java

El código Javascript que da la funcionalidad de chat a esta página está en el archivo js/chat.js:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108$(documento). ready(function(){var chatHistory =$(".chat-history");var chatHistoryList = chatHistory.find("ul");var sendBtn =$("#send-btn");var ndaBtn =$("#request-nda-btn");var textarea =$("#message-to-send");functionaddMessage(){var messageToSend = textarea. val().trim();if(messageToSend !==""){ $.ajax({ method: "POST", url:"/chat/message", contentType: "application/json; charset=UTF-8", data:JSON.stringify({ message: messageToSend })}).done(function(msg){console.log(msg); textarea. val("");});}}functionscrollToBottom(){ chatHistory.scrollTop(chatHistory[0].scrollHeight);}functionaddSystemMessage(message){var template =Handlebars.compile($("#message-system-template").html());var params ={ msg: message }; chatHistoryList. append(template(params));scrollToBottom();}scrollToBottom();var pusher =newPusher(PUSHER_KEY,{ encrypted:true, authEndpoint:"/chat/auth"});var presenceChannel = pusher.subscribe(CHANNEL); presenceChannel.bind("pusher:subscription_succeeded",function(){console.log(presenceChannel. members.me);addSystemMessage("Te has unido al chat");}); presenceChannel.bind("pusher:subscription_error",function(status){alert("La suscripción al canal falló con el estado "+ status);}); presenceChannel.bind("pusher:member_added",function(member){console. log("pusher:member_added");addSystemMessage(member.info.name+" se ha unido al chat");}); presenceChannel.bind("pusher:member_removed",function(member){console.log("pusher:member_removed");addSystemMessage(member.info.name+" ha salido del chat");}); presenceChannel. bind("new_message",function(data){if(data.message!==""){var templateEl = presenceChannel.members.me.id=== data.userId?$("#message-template"):$("#message-response-template");var template =Handlebars.compile(templateEl.html());var params ={ msg: data.message.replace(/(r?
)/g,"&lt;br /&gt;"), nombre: data.userName, time: data.time}; chatHistoryList.append(template(params));scrollToBottom();}}); presenceChannel.bind("system_message",function(data){if(data.message!==""){addSystemMessage(data.message);}}); sendBtn. on("click",function(){addMessage();}); ndaBtn.on("click",function(){ $.ajax({ method: "POST", url:"/chat/request/nda"}).done(function(msg){console. log(msg);});});$(documento).ajaxStart(function(){$(".loader").show();}).ajaxStop(function(){$(".loader").hide();});});

javascript

Después de definir las funciones para añadir mensajes, podemos crear el objeto empujador.

1234var pusher =newPusher(PUSHER_KEY,{ encriptado:true, authEndpoint:"/chat/auth"});

javascript

Entonces, la suscripción al canal se hace y los eventos de presencia se vinculan.

Los puntos finales de la API de la aplicación para la funcionalidad del chat están definidos en el com.example.web.PusherController.

Esta clase está anotada con la anotación @RestController:

1234@RestControllerpublicclassPusherController{...}

java

En el MVC 4 de primavera, si su controlador está anotado con @RestController en lugar de @Controller, no necesita la anotación @ResponseBody para especificar las respuestas formateadas como JSON.

Vamos a cablear los servicios que necesitaremos (note la anotación@PostConstrucción en el método que crea la instancia de empuje):

123456789101112131415161718192021222324@RestControllerpublicclassPusherController{privateLogger logger =LoggerFactory.getLogger(PusherController. class);@AutowiredprivateChatService chatService;@AutowiredprivateUserService userService;@AutowiredprivatePusherSettings pusherSettings;privatePusher pusher; /** * Método ejecutado después de crear el objeto * que crea una instancia del objeto Pusher */@PostConstructpublicvoidcreatePusherObject(){ pusher = pusherSettings.newInstance();}}

java

Después de eso, definamos el punto final de autenticación para el chat de presencia:

1234567891011121314151617181920@RestControllerpublicclassPusherController{...@RequestMapping(method =RequestMethod.POST, value="/chat/auth")publicStringauth(@RequestParam(value="socket_id")String socketId,@RequestParam(value="channel_name")String channel,@SessionAttribute(GeneralConstants. ID_SESSION_CHAT_INFO)ChatForm chatInfo){Long userId = chatInfo.getIdUser();Map&lt;String,String&gt; userInfo =newHashMap&lt;&gt;(); userInfo.put("nombre", chatInfo. getUserName()); userInfo.put("email", chatInfo.getUserEmail());String res = pusher.authenticate(socketId, channel,newPresenceUser(userId, userInfo));return res;}}

java

En el método, sólo obtenemos la información sobre el chat y el usuario de la sesión para hacer la autenticación real y devolver la información.

Para registrar un mensaje, lo insertamos en la base de datos y publicamos un evento en el canal de presencia después:

123456789101112131415161718192021222324252627282930@RestControllerpublicclassPusherController{...@RequestMapping(value ="/chat/message", method =RequestMethod. POST, consume ="application/json", produce ="application/json")publicChatMessageResponsemesssage(@RequestBodyChatMessageRequest request,@SessionAt(GeneralConstants.ID_SESSION_CHAT_INFO)ChatForm chatInfo){Message msg =newMessage(); msg.setCreatedAt(newDate()); msg. setIdChat(chatInfo.getIdChat()); msg.setMessage(request.getMessage()); chatService.saveMessage(msg, chatInfo.getIdUser());ChatMessageResponse response =newChatMessageResponse(); response.setMessage(msg.getMessage()); response. setTime(msg.getCreatedAtString()); response.setUserId(msg.getUser().getId()); response.setUserName(msg.getUser().getName()); pusher.trigger(chatInfo.getPresenceChatName(), "new_message", response);return response;}}

java

Finalmente, necesitamos definir el método que manejará las solicitudes de webhook de Pusher:

123456789101112131415161718192021222324252627282930313233343536373839404142@RestControllerpublicclassPusherController{...@RequestMapping(value ="/pusher/webhook", method =RequestMethod. POST, consume ="application/json")publicStringwebhook(@RequestHeader(value="X-Pusher-Key")String key,@RequestHeader(value="X-Pusher-Signature")String signature,@RequestBodyString json)lanzaJsonParseException,JsonMappingException,IOException{Validez válida = empujador. validarFirma de GanchoWeb(clave, firma, json);si(Validez.VALIDA.es igual a(válido)){Mapper mapeador de objetos = nuevoMapperObjecto();Solicitud de GanchoWeb del Empujador = mapper.readValue(json,Solicitud de GanchoWeb del Empujador. class);if(request.getEvents()!=null){for(PusherWebhookRequest.Event event : request.getEvents()){switch(event.getName()){case "channel_occupied": logger.info("channel_occupied: "+ event.getChannel());break;case "channel_vacated": logger.info("channel_vacated: "+ event.getChannel()); chatService.markChatAsInactive(event.getChannel().replace(GeneralConstants.CHANNEL_PREFIX,""));break;case "member_added": logger.info("member_added: "+ event.getUserId());break;case "member_removed": logger.info("member_removed: "+ evento.getUserId()); userService.markUserAsInactive(event.getUserId());break;}}}}regresar "OK";}}

java

En el código de arriba, verificamos que la solicitud proviene de Pusher. Los WebHooks válidos contendrán estos encabezados:

  • X-Pusher-Key: La llave API del Empujador actualmente activa.
  • Firma del Empujador X: Un compendio hexagonal HMAC SHA256 formado por la firma de la carga útil POST (cuerpo) con el secreto de la ficha APIS del Empujador

Para realizar la autenticación, la biblioteca de Pusher requiere estos encabezados y el cuerpo de la solicitud como argumentos para validar el método de WebhookSignature:

1Validez válida = pusher.validateFirma de gancho web (clave, firma, json);

java

Si la petición es válida, el objeto JSON se convierte en un objeto de tipo PusherWebhookRequest. Estas son solicitudes de muestra en formato JSON:

123456789101112131415161718192021{"time_ms":1469203501957, "events":[{"channel": "presence-test", "name": "channel_occupied"}]}{"time_ms":1469203501957, "events":[{"channel": "presence-test", "user_id": "1", "name": "member_added"}]}

javascript

Finalmente, el evento se maneja de acuerdo a esto (para eventos channel_vacated el chat se marca como inactivo y para eventos member_removed, el usuario también se marca como inactivo).

La funcionalidad del chat está completa, ahora lo único que falta es la firma del documento NDA.