Redis caching and Websockets

Redis is used for local caching to cache axios requests via axios-cache-adapter, and to provide a subscriber and publisher for Websockets ws.

Caching

Request caching is currently implemented on a couple of the JWPlayer routes, see Integration Routes.

Redis caching is used to allow the cache to be usable for multiple client if required.

Through axios-cache-adapater an axios instance that requests via a redis store can be setup very simply:

const store = new RedisStore(client);
const axiosInstance = setup({
  cache: {
    maxAge: 1000 * 60 * 5,
    store,
  },
});

Websockets

Websockets in the CMS are used for both keeping content upto date with the Drupal API and to provide concurrent editing in the Layout Manager and entity building with Field locking.

A Websocket server is initiated and added to the express App in order that I can be referenced later, along with a Redis event publisher, and a subscriber listening to a default REDIS_REALTIME_CHANNEL.

const server = http.createServer(app);
const wss = new WebSocket.Server({ perMessageDeflate: false, server });
server.listen(process.env.WEBSOCKET_PORT || WS_PORT);
const REDIS_SERVER = process.env.REDIS_CMS_ENDPOINT || REDIS_LOCAL_ENDPOINT;

const redisSubscriber = redis.createClient(REDIS_SERVER);
const redisPublisher = redis.createClient(REDIS_SERVER);
redisSubscriber.subscribe(REDIS_REALTIME_CHANNEL);

app.use((req, res, next) => {
  app.__wss = wss;
  app.__redisPublisher = redisPublisher;
  next();
});

A helper file for events, /route/helper/js provides the following dispatch function that publish an event via Redis that are picked up by the websocket server and served to any clients that have subscribed to those events.

dispatchArticleListUpdate

This dispatches a websocket event triggers the Redis action:

{
  type: WS_ARTICLE_LIST,
  value: articleId
}

This will be pick up by RxJS pipeline that are active if a user is on the Article List or on an Article edit page with a matching id, and a new copy of the article/article list data will be requested from the Drupal API.

dispatchLayoutUpdate

This dispatches a websocket event triggers the Redis action:

{
  type: WS_LAYOUT,
  value: { vocab, id, clientId }
}

This will be pick up by RxJS pipeline that are active if a user is on the layout List for the specified vocabulary or on a layout edit page with matching id, and a new copy of the layout/layout list data will be requested from the Drupal API.

dispatchProductListUpdate

This dispatches a websocket event triggers the Redis action:

{
  type: WS_PRODUCT_LIST,
}

This will be pick up by RxJS pipeline that are active if a user is on the product List, and a new copy of the product list data will be requested from the DrupalAPI.

dispatchLiveblogUpdate

This dispatches one of the following websocket event triggers the Redis action:

{
  type: WS_LIVEBLOG_LIST,
  value: liveblogId
}

This will be pick up by RxJS pipeline that are active if a user is on an article page with the attached liveblog or on the liveblog edit page with the matching id, and a new copy of the liveblog data will be requested from the DrupalAPI.

{
  type: WS_LIVEBLOG_CONTAINER,
}

This will be pick up by RxJS pipeline that are active if a user is on the liveblog List, and a new copy of the liveblog list data will be requested from the DrupalAPI.

dispatchVocabListUpdate

This dispatches a websocket event triggers the Redis action:

{
  type: WS_VOCAB_LIST,
  value: { vocab }
}

This will be pick up by RxJS pipeline that are active if a user is on the layout List of the respective vocab, and a new copy of the layout/vocab list data will be requested from the DrupalAPI.

sendMessage from RxJS

Websockets can also have events broadcast through a Redux action or as part of a RxJS pipeline with the sendMessage function /src/utils/sendMessage.js:

sendMessage({ type, value });

The object should match the Redux action to be broadcast to the clients.

If the type is one of the field locking action types:

  • FIELD_LOCK_SUBSCRIBE
  • SESSION_UNLOCK_FIELD
  • SESSION_LOCK_FIELD
  • WS_FIELD_LOCK_SET
  • WS_FIELD_LOCK_UNSET
  • WS_FIELD_LOCK_REFRESH

the Websocket server will only send the action if the client websocket id is not the same as the sender websocket id and the contentId matches.