The trick to getting XHR requests to work with Elm or Javascript when developing with Safari

My development of my latest Elm app came crashing to a halt when suddenly pages that had been working fine started returning 401 Unauthorised HTTP errors.

I was writing a Single Page Application (SPA) that was using XHR (Ajax) requests to retrieve information from an existing Rail web site. The SPA was served up from a local test server whilst the API was served from a different host. The behaviour seemed to have no pattern, sometimes the requests went through fine, sometimes the dreaded 401 error.

Inspecting the network requests in the developer console, the problem was obvious – sometimes the requests were sending the session cookie, and sometimes not. I could make the app fail every time by deleting the cookies associated with the API web site. Once I had done this, I couldn’t log in. However, if I opened the API web site in a different tab, and logged in (and even out), my SPA app would now send the session cookie and work fine.

The problem was my privacy settings in Safari. I had chosen Allow from Websites I visit. From the documentation:

Allow from Websites I Visit – Allow all first-party cookies and block all third-party cookies unless that third party was a first party at one time (based on current cookies and browsing history).

The issue came down to the trust being based on my previous browsing activity. Simply visiting the API website in my browser was enough to flag the cookie as a first-party cookie. The SPA then could send the cookie. If that site visit wasn’t in my history (and this seems to be recorded along with the cookie in the cookie store), Safari wouldn’t send the cookie to the third party API website.

So, if you’re developing a Javascript application that uses XHR and testing in Safari, check your Cookie privacy settings, or make a manual visit the API server to enable the cookies to be sent by your application.

Digging into React and Redux with Rails 5’s ActionCable

One of the exiting new technologies in Rails 5 is ActionCable, essentially an implementation of web sockets that allows real time communication between client and server.

We’ve been re-implementing our helpdesk system by rebuilding the user interface as a React application.  A majority of the user interaction is viewing lists of tickets, which are being constantly updated by the helpdesk staff.  This seemed a perfect use of ActionCable to allow for live updating of people’s ticket lists.

The majority of tutorials embed the React app into the Rails app using gems such as react-rails.  This gives you access to the Rails generated Javascript App object for making ActionCable calls, and defining what happens when ActionCable messages are sent back inside of the Rails app using Coffeescript, but I wanted to build a more pure ReactJS app separate from the Rails application.

Our app is based on Redux, so the concept we wanted to investigate was sending updates to the server as an ActionCable message, and receive updated tickets back over the ActionCable channel, and have those feed back into the Redux store.

Rails and ActionCable

To start on the server side, we created a very simple ActionCable channel.  We skip the user authentication and separation of streams by user and simply broadcast all ticket changes to all subscribers.  We define two methods, send_all_tickets which can be used so set the initial store state (and for forcing refreshes) and new_ticket which is called when a ticket is added to the system.  In our full system we would add this ticket into the database, but for this proof of concept we just broadcast the new ticket out to all subscribers.

require 'securerandom'

class TicketsChannel < ApplicationCable::Channel
  def subscribed
    stop_all_streams
    stream_from "tickets"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def send_all_tickets
    Ticket.all.each do |ticket|
      message_data = {
        action: 'new_ticket',
        ticket: ticket
      }
      ActionCable.server.broadcast('tickets',message_data)
    end
  end


  def new_ticket(ticket)
    # normally would create a ticket in the database here
    
    ticket = {
      subject: ticket["ticket"],
      id: SecureRandom.uuid
    }
    message_data = {
      action: 'new_ticket',
      ticket: ticket,
    }
    ActionCable.server.broadcast('tickets',message_data)
  end

  private


end

On the React side, we assume we are working with a browser that supports WebSockets natively.

We start by setting up React and importing our Redux action for adding tickets to the store.

import { addTicketToStore } from '../actions'

We then implemented a simple WebSocket interface for receiving messages from ActionCable and for sending messages to the server and store it in the component state.


  componentDidMount() {
 
    let ws= new WebSocket("ws:localhost:3000/cable" )
    ws.onopen = function() {
        let identifier = JSON.stringify({channel:'TicketsChannel'})
        let msg = JSON.stringify({command:'subscribe', identifier:identifier})
        ws.send(msg);
     };

     ws.onmessage = (evt) => {
        var received_msg = evt.data;
        this.process_message(received_msg)
     }

     ws.onclose = function()
     {
        // websocket is closed.
        console.log("Connection is closed...");
     };

     this.setState({ws: ws})
  }

Any messages we receive from the Rails server are processed and the message information extracted out, and if the message is a new ticket, we add the ticket into the Redux store:

  process_message(received_msg) {

    let parsedMessage = JSON.parse(received_msg)
    if (parsedMessage["identifier"]!="_ping") {
      console.log("Message is received..." +  received_msg);
      if (parsedMessage["message"]) {
        let message=parsedMessage["message"]
        if (message['action']=='new_ticket') {
          this.props.dispatch(addTicketToStore(message["ticket"]))
        }
      }
    }

  }

and here we send a message to the server to retrieve all tickets

  get_all_tickets() {
    console.log('send request for all tickets')
    let identifier = JSON.stringify({channel:"TicketsChannel"})
    let data = JSON.stringify({action:'send_all_tickets'})
    let msg = JSON.stringify({command:'message', identifier:identifier,data:data})
    this.state.ws.send(msg);
  }

 

We render a simple interface for listing our tickets from the store :

 



      <div className="main-container">

        <div className="container">
          <a href='#' onClick={e => {
              e.preventDefault()
              console.log("refresh")
              this.get_all_tickets()
            }}>Refresh All tickets</a>
          <h3>Tickets:</h3>
          <ul>
          {this.props.tickets.map(function(ticket){
            return <li key={ticket.id}>{ticket.subject}</li>;
          })}
          </ul>
        </div>
        <div>

 

and finally render a simple interface to submit new tickets by sending a message to the server:

 


      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        let identifier = JSON.stringify({channel:"TicketsChannel"})
        let data = JSON.stringify({action:'new_ticket', ticket:input.value})
        let msg = JSON.stringify({command:'message', identifier:identifier,data:data})

        this.state.ws.send(msg);
        input.value = ''
        }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Add Ticket
        </button>
      </form>

We end up with an interface like this:

Tickets List

As a new ticket subject is typed and “Add Ticket” is clicked, every browser open to this page updates with the new ticket added to the list.

This is just the beginning of how we see our Rails app and our ReactJS Redux app interacting, but we’re very excited but what’s possible. Our next steps are to make a generalised ActionCable component to feed actions to the store.

You can download the whole project from github here