PubNub Live Chat Beta Testing

UserCP Screeching Frog 0.7591

  • Minor internal changes to styles and CSS only.

This this SF release 0.7591, will not be updating Live Chat (LC) for a few days or longer, since the core features are basically done and there are other big fish to fry. Plus, I need to examine, over a week period, how many PubNub transactions in the current configuration, and if I need to recode to conserve unnecessary PubNub transactions (optimize).

Then, after that is done, need to determine how or if to use the same for user-to-user conversations in place of our aging PM system.

UserCP Screeching Frog 0.7593

  • Found an issue with too many keepalives generating unnecessary traffic from client to pubnub network, so I changed the keepalives, and tweaked other heartbeat params:

Please clear your cache (and restart most browsers), to save our pubnub transactions quota. Thanks!

Update:

There is still an issue with PubNub Javascript SDK generating too much network traffic with heartbeats and presence ping every few seconds.

This issue does not effect the live chat page, per se, but it does generate too many transactions and I am planning to work with PubNub to (hopefully) correct this.

I am seeing 1000s of truncations where there should be a handful and this is not scalable or efficient. I hope the issue is because I am doing something incorrect in the configuration.

UserCP Screeching Frog 0.7594

  • Added "snap scroll bar code to top or bottom of chat window on (1) incoming message or (2) enter key for send message.
adjustScroll() {
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      if (!this.reverseorder) scroll.scrollTop = 0;
      else scroll.scrollTop = scroll.scrollHeight;
    },

Seems to work fine in Chome but not so great in Safari.

UserCP Screeching Frog 0.7596

  • Minor scrollbar snap tweaks on init.
  • Disable pubnub presence in subscribe() for testing to see if network load will decrease and all work as it should.

UserCP Screeching Frog 0.7598

  • Disabled unused service workers.
  • Disable presense in pubnub listeners (testing ways to reduce network XHR calls).

UserCP Screeching Frog 0.7599

  • Force scroll to top or bottom of browser window on reload, new message, or toogle.
1 Like

Here is the my current Vue.js UserCP (v0.7691) Live Chat code (not yet optimized, so please forgive my mess) using PubNub:

<template>
  <div class="bottomfill">
    <div class="d-flex justify-content-start flex-wrap room nomargin togglearea">
      <div class="checkbox leftmargin">
        <label>
          <input class="ckbox" type="checkbox" v-model="reverseorder">
          <span v-bind:class="{reverseon: this.reverseorder}" class="toggler">Toggle Message Order</span>
          <span v-if="this.reverseorder">
            <i v-bind:class="{reverseon: this.reverseorder}" class="tim-icons icon-minimal-down"></i>
          </span>
          <span v-else>
            <i class="tim-icons icon-minimal-up"></i>
          </span>
        </label>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room">
      <div class="col-md-12 box">
        <div
          class="neo-chat-title"
          v-if="this.psoccupancy"
        >Live Chat Participants: {{ this.psoccupancy}}</div>

        <div class="neo-chat-title">{{ this.userlist}}</div>
        <VueSteamChat :messages="this.msglist" @vue-steam-chat-on-message="onNewMessage"/>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room channelbar">
      <div>Channels</div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="livebox">
          <span v-bind:class="{liveon: this.livebox}">Live</span>
        </label>
      </div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="postsbox">
          <span v-bind:class="{liveon: this.postsbox}">Posts</span>
        </label>
      </div>
      <div class="checkbox disabled">
        <label>
          <input class="ckbox" type="checkbox" v-model="statusbox">
          <span v-bind:class="{liveon: this.statusbox}">Status</span>
        </label>
      </div>
    </div>
  </div>
</template>

<script>
    function init() {
        sessionStorage.setItem("loaded", true);
    }
    window.onload = init;
    // import Vuex from "vuex";
    // Vue.use(Vuex);

    import Vue from "vue";
    import VueSteamChat from "vue-steam-chat";
    import {
        setTimeout,
        setInterval,
        clearInterval
    } from "timers";
    var PubNub = require("pubnub");
    require("vue-steam-chat/dist/index.css");
    var interval = null;
    var vbchat = "";

    if (window.vbname == "Unregistered") {
        const d = Math.floor(Math.random() * 100);
        if (localStorage.getItem("randomuser")) {
            vbchat = "Guest " + localStorage.getItem("randomuser");
        } else {
            const d = Math.floor(Math.random() * 100);
            localStorage.setItem("randomuser", d);
            vbchat = "Guest " + localStorage.getItem("randomuser");
        }
    } else {
        vbchat = window.vbname;
    }

    var pubnub = new PubNub({
        subscribeKey: "demo",
        publishKey: "demo",
        ssl: true,
        keepAlive: true,
        // heartbeatInterval: 10,
        presenceTimeout: 300,
        restore: true,
        uuid: vbchat
    });
    var debounce = require("debounce");
    export default {
        name: "vue-steam-chat",
        components: {
            VueSteamChat
        },
        computed: {
            msglist() {
                var listarray = [];
                //console.log("this.text ", this.text);
                var that = this;
                this.text.forEach(function(element) {
                    if (
                        element.text.match(/Forum Replied To/g) ||
                        element.text.match(/Forum Started Post/g)
                    ) {
                        if (that.postsbox == true) {
                            listarray.push(element);
                        }
                    } else if (element.text.match(/Forum Status/g)) {
                        if (that.statusbox == true) {
                            listarray.push(element);
                        }
                    } else if (that.livebox == true) {
                        listarray.push(element);
                    }
                });
                if (this.reverseorder == true) {
                    return listarray.reverse();
                } else {
                    return listarray;
                }
                //this.adjustScroll();
            },
            uuid() {
                return pubnub.getUUID();
            }
        },
        data() {
            return {
                blockpublish: false,
                livebox: true,
                reverseorder: true,
                postsbox: true,
                statusbox: true,
                historydone: false,
                userlist: "",
                userarray: [],
                uuida: window.vbuserId,
                uuname: window.vbusername,
                ch1: null,
                time: Date.now() / 1000,
                chathist: [],
                laststatuschange: [{}],
                occupancy: 0,
                psoccupancy: 0,
                initdone: 0,
                status: 0,
                text: [{
                        time: 1506117496,
                        username: "Gaben",
                        text: "Chat initialized ..."
                    },
                    {
                        time: 1506117500,
                        username: "Solo",
                        text: "So, say something !!"
                    },
                    {
                        time: 1506117530,
                        username: "Neo",
                        text: "Hmmm ..."
                    }
                ]
            };
        },
        methods: {
            adjustScroll() {
                var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
                var view = document.querySelector(".bottomfill");
                var toggle = document.querySelector(".togglearea");
                var channels = document.querySelector(".channelbar");

                if (!this.reverseorder) {
                    scroll.scrollTop = 0;
                    //$("html,body").animate({ scrollTop: 0 }, "slow");
                    // view.scrollIntoView(true);
                    view.scrollIntoView({
                        behavior: "smooth",
                        block: "start",
                        inline: "nearest"
                    });
                } else {
                    scroll.scrollTop = scroll.scrollHeight;
                    // channels.scrollIntoView(false);
                    channels.scrollIntoView({
                        behavior: "smooth",
                        block: "end",
                        inline: "nearest"
                    });
                    //$("html,body").animate({ scrollTop: scroll.scrollHeight }, "slow");
                }
            },
            initScroll() {
                var view = document.querySelector(".bottomfill");
                var channels = document.querySelector(".channelbar");
                var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
                //$("html,body").animate({ scrollTop: scroll.scrollHeight }, "slow");
                //channels.animate({ scrollIntoView: false }, "slow");
                view.scrollIntoView({
                    behavior: "smooth",
                    block: "end",
                    inline: "nearest"
                });
            },
            getHereNow() {
                var that = this;
                pubnub.hereNow({
                        channels: ["ch1"],
                        // channelGroups : ["cg1"],
                        includeUUIDs: true,
                        includeState: true
                    },
                    function(status, response) {
                        that.psoccupancy = response.totalOccupancy;
                        // console.log("herenow: ", response);
                        var allusers = "";
                        var index = 0;

                        response.channels.ch1.occupants.forEach(function(user) {
                            if (response.channels.ch1.occupants) {
                                that.userarray.push(user);
                                if (index == 0) allusers = user.uuid;
                                else allusers = allusers + ", " + user.uuid;
                            }
                            index++;
                            that.userlist = allusers;
                        });
                    }
                );
            },
            gmCallback(status, response) {
                console.log("gm callback", status, response);
            },
            history() {
                var that = this;
                if (pubnub) {
                    pubnub.history({
                            reverse: false,
                            channel: "ch1",
                            count: 100, // how many items to fetch
                            stringifiedTimeToken: true // false is the default
                        },
                        function(status, response) {
                            if (status.error == false) {
                                response.messages.forEach(function(element) {
                                    if (element.entry.username && element.entry.text != undefined) {
                                        if (
                                            element.entry.text.match(/Forum Replied To/g) ||
                                            element.entry.text.match(/Forum Started Post/g)
                                        ) {
                                            if (that.postsbox == true) {
                                                that.text.push(element.entry);
                                            }
                                        } else if (element.entry.text.match(/Forum Status/g)) {
                                            if (that.statusbox == true) {
                                                that.text.push(element.entry);
                                            }
                                        } else if (that.livebox == true) {
                                            that.text.push(element.entry);
                                        }
                                    }
                                });
                                that.historydone = true;
                            } else {
                                console.log("error ", status);
                            }
                        }
                    );
                }
                that.initScroll();
            },
            controlMessage(message) {
                var that = this;
                if (message.length < 2) {
                    return false;
                }
                let m = {
                    time: Date.now() / 1000,
                    username: this.uuid,
                    text: message
                };
                var publishConfig = {
                    sendByPost: true,
                    channel: "controlreply",
                    message: m
                };

                pubnub.publish(publishConfig, function(status, response) {
                    if (status.error) {
                        console.log(status);
                    } else {
                        // console.log("control message Published w/ timetoken", JSON.stringify(publishConfig));
                    }
                });
            },

            onNewMessage(message) {
                var that = this;
                if (message.length < 2) {
                    return false;
                }
                let m = {
                    time: Date.now() / 1000,
                    username: this.uuid,
                    text: message
                };
                var publishConfig = {
                    sendByPost: true,
                    channel: "ch1",
                    message: m
                };

                if (that.blockpublish != true) {
                    pubnub.publish(publishConfig, function(status, response) {
                        if (status.error) {
                            console.log(status);
                        } else {
                            // console.log("message Published w/ timetoken", response.timetoken);
                        }
                    });
                    that.getHereNow();
                    // that.adjustScroll();
                }
            }
        },
        created() {
            $.LoadingOverlay("show");
            pubnub.subscribe({
                channels: ["ch1", "control"],
                withPresence: false
            });
        },
        updated() {
            debounce(this.getHereNow, 300, false);
            this.adjustScroll();
        },
        beforeDestroy() {
            pubnub.unsubscribeAll();
            // pubnub.unsubscribe({
            //   channels: ["ch1", "control"]
            // });
            var existingListener = {
                message: function() {}
            };

            if (existingListener) pubnub.removeListener(existingListener);
            if (interval) {
                clearInterval(interval);
            }
        },
        async mounted() {
            if (localStorage.blc) {
                if (localStorage.getItem("blc") == "true") {
                    this.blockpublish = true;
                }
            }
            if (localStorage.status_msgs) {
                if (localStorage.getItem("status_msgs") == "yes") {
                    this.statusbox = true;
                }
            }
            var that = this;
            that.history();

            pubnub.addListener({
                // status: function(statusEvent) {
                //   if (statusEvent.category === "PNConnectedCategory") {
                //     var newState = {
                //       name: this.uuid,
                //       timestamp: Date.now()
                //     };
                //     pubsub.setState({
                //       channels: ["ch1"],
                //       state: newState
                //     });
                //   }
                // },
                message: function(m) {
                    // console.log("livechat msg channel received: ", JSON.stringify(m));
                    that.adjustScroll();
                    if (m.channel == "ch1") {
                        if (
                            m.message.text.match(/Forum Replied To/g) ||
                            m.message.text.match(/Forum Started Post/g)
                        ) {
                            if (that.postsbox == true) {
                                that.text.push(m.message);
                            }
                        } else if (m.message.text.match(/Forum Status/g)) {
                            if (that.statusbox == true) {
                                that.text.push(element.entry);
                            }
                        } else if (that.livebox == true) {
                            that.text.push(m.message);
                        }
                    } 

                     // 
                    // Removed lines for internal use only
                    //
   
                        that.getHereNow();
                    }
                    //that.text.push(m.message);
                    //console.log("newmsg: ", m.message);
                }
                // presence: function(presenceEvent) {
                //   that.psoccupancy = presenceEvent.occupancy;
                //   const i = Date.now();
                //   that.getHereNow();
                // }
            });
            this.getHereNow();
            var mythis = this;
            $(function() {
                var input = document.querySelector(".vue-steam-chat__textarea");
                var button = document.querySelector(".vue-steam-chat__button");
                var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
                input.addEventListener("keyup", function(event) {
                    if (event.keyCode === 13) {
                        event.preventDefault();
                        button.click();
                    }
                });
                $.LoadingOverlay("hide");
            });
        }
    };
</script>
<style scoped>
    .box {
        margin: 0px !important;
        height: 500px;
        margin: 20px;
    }

    .neo-chat-title {
        color: slategray;
        font-size: 1.2em;
        text-align: center;
        margin: 10px;
    }

    .room {
        margin-bottom: 20px;
    }

    .ckbox {
        margin-right: 7px;
        background-color: cornflowerblue;
    }

    .reverseon {
        color: cornflowerblue;
    }

    .liveon {
        color: cornflowerblue;
    }

    .ckbox {
        color: cornflowerblue;
    }

    .nomargin {
        margin: 0px 0px 0px 0px;
    }

    .leftmargin {
        margin-left: 30px;
    }

    .toggler {
        margin: 10px 10px 0px 0px;
    }

    .channelbar {
        margin: 100px 10px 10px 10px;
        padding-bottom: 10px;
    }

    .bottomfill {
        margin-bottom: 0px;
        padding-top: 20px;
    }
</style>

version 0.7062... minor changes

<template>
  <div class="bottomfill">
    <div class="d-flex justify-content-start flex-wrap room nomargin togglearea">
      <div class="checkbox leftmargin">
        <label>
          <input class="ckbox" type="checkbox" v-model="reverseorder">
          <span v-bind:class="{reverseon: this.reverseorder}" class="toggler">Toggle Message Order</span>
          <span v-if="this.reverseorder">
            <i v-bind:class="{reverseon: this.reverseorder}" class="tim-icons icon-minimal-down"></i>
          </span>
          <span v-else>
            <i class="tim-icons icon-minimal-up"></i>
          </span>
        </label>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room">
      <div class="col-md-12 box">
        <div
          class="neo-chat-title"
          v-if="this.psoccupancy"
        >Live Chat Participants: {{ this.psoccupancy}}</div>

        <div class="neo-chat-title">{{ this.userlist}}</div>
        <VueSteamChat :messages="this.msglist" @vue-steam-chat-on-message="onNewMessage"/>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room channelbar">
      <div>Channels</div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="livebox">
          <span v-bind:class="{liveon: this.livebox}">Live</span>
        </label>
      </div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="postsbox">
          <span v-bind:class="{liveon: this.postsbox}">Posts</span>
        </label>
      </div>
      <div class="checkbox disabled">
        <label>
          <input class="ckbox" type="checkbox" v-model="statusbox">
          <span v-bind:class="{liveon: this.statusbox}">Status</span>
        </label>
      </div>
    </div>
  </div>
</template>

<script>
function init() {
  sessionStorage.setItem("loaded", true);
}
window.onload = init;
// import Vuex from "vuex";
// Vue.use(Vuex);

import Vue from "vue";
import VueSteamChat from "vue-steam-chat";
import { setTimeout, setInterval, clearInterval } from "timers";
var PubNub = require("pubnub");
require("vue-steam-chat/dist/index.css");
var interval = null;
var vbchat = "";

if (window.vbname == "Unregistered") {
  const d = Math.floor(Math.random() * 100);
  if (localStorage.getItem("randomuser")) {
    vbchat = "Guest " + localStorage.getItem("randomuser");
  } else {
    const d = Math.floor(Math.random() * 100);
    localStorage.setItem("randomuser", d);
    vbchat = "Guest " + localStorage.getItem("randomuser");
  }
} else {
  vbchat = window.vbname;
}

var pubnub = new PubNub({
  subscribeKey: "demo",
  publishKey: "demo",
  ssl: true,
  keepAlive: true,
  // heartbeatInterval: 10,
  presenceTimeout: 300,
  restore: true,
  uuid: vbchat
});
var debounce = require("debounce");
export default {
  name: "vue-steam-chat",
  components: {
    VueSteamChat
  },
  computed: {
    msglist() {
      var listarray = [];
      //console.log("this.text ", this.text);
      var that = this;
      this.text.forEach(function(element) {
        if (
          element.text.match(/Forum Replied To/g) ||
          element.text.match(/Forum Started Post/g)
        ) {
          if (that.postsbox == true) {
            listarray.push(element);
          }
        } else if (element.text.match(/Forum Status/g)) {
          if (that.statusbox == true) {
            listarray.push(element);
          }
        } else if (that.livebox == true) {
          listarray.push(element);
        }
      });
      if (this.reverseorder == true) {
        return listarray.reverse();
      } else {
        return listarray;
      }
      //this.adjustScroll();
    },
    uuid() {
      return pubnub.getUUID();
    }
  },
  data() {
    return {
      blockpublish: false,
      livebox: true,
      reverseorder: true,
      postsbox: true,
      statusbox: true,
      historydone: false,
      userlist: "",
      userarray: [],
      uuida: window.vbuserId,
      uuname: window.vbusername,
      ch1: null,
      time: Date.now() / 1000,
      chathist: [],
      laststatuschange: [{}],
      occupancy: 0,
      psoccupancy: 0,
      initdone: 0,
      status: 0,
      text: [
        {
          time: 1506117496,
          username: "Gaben",
          text: "Chat initialized ..."
        },
        {
          time: 1506117500,
          username: "Solo",
          text: "So, say something !!"
        },
        {
          time: 1506117530,
          username: "Neo",
          text: "Hmmm ..."
        }
      ]
    };
  },
  methods: {
    adjustScroll() {
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      var view = document.querySelector(".bottomfill");
      var toggle = document.querySelector(".togglearea");
      var channels = document.querySelector(".channelbar");

      if (!this.reverseorder) {
        scroll.scrollTop = 0;
        //$("html,body").animate({ scrollTop: 0 }, "slow");
        // view.scrollIntoView(true);
        view.scrollIntoView({
          behavior: "smooth",
          block: "start",
          inline: "nearest"
        });
      } else {
        scroll.scrollTop = scroll.scrollHeight;
        // channels.scrollIntoView(false);
        channels.scrollIntoView({
          behavior: "smooth",
          block: "end",
          inline: "nearest"
        });
        //$("html,body").animate({ scrollTop: scroll.scrollHeight }, "slow");
      }
    },
    initScroll() {
      var view = document.querySelector(".bottomfill");
      var channels = document.querySelector(".channelbar");
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      //$("html,body").animate({ scrollTop: scroll.scrollHeight }, "slow");
      //channels.animate({ scrollIntoView: false }, "slow");
      view.scrollIntoView({
        behavior: "smooth",
        block: "end",
        inline: "nearest"
      });
    },
    getHereNow() {
      var that = this;
      pubnub.hereNow(
        {
          channels: ["ch1"],
          // channelGroups : ["cg1"],
          includeUUIDs: true,
          includeState: true
        },
        function(status, response) {
          that.psoccupancy = response.totalOccupancy;
          // console.log("herenow: ", response);
          var allusers = "";
          var index = 0;

          response.channels.ch1.occupants.forEach(function(user) {
            if (response.channels.ch1.occupants) {
              that.userarray.push(user);
              if (index == 0) allusers = user.uuid;
              else allusers = allusers + ", " + user.uuid;
            }
            index++;
            that.userlist = allusers;
          });
        }
      );
    },
    gmCallback(status, response) {
      console.log("gm callback", status, response);
    },
    history() {
      var that = this;
      if (pubnub) {
        pubnub.history(
          {
            reverse: false,
            channel: "ch1",
            count: 100, // how many items to fetch
            stringifiedTimeToken: true // false is the default
          },
          function(status, response) {
            if (status.error == false) {
              response.messages.forEach(function(element) {
                if (element.entry.username && element.entry.text != undefined) {
                  if (
                    element.entry.text.match(/Forum Replied To/g) ||
                    element.entry.text.match(/Forum Started Post/g)
                  ) {
                    if (that.postsbox == true) {
                      that.text.push(element.entry);
                    }
                  } else if (element.entry.text.match(/Forum Status/g)) {
                    if (that.statusbox == true) {
                      that.text.push(element.entry);
                    }
                  } else if (that.livebox == true) {
                    that.text.push(element.entry);
                  }
                }
              });
              that.historydone = true;
            } else {
              console.log("error ", status);
            }
          }
        );
      }
      that.initScroll();
    },
    controlMessage(message) {
      var that = this;
      if (message.length < 2) {
        return false;
      }
      let m = {
        time: Date.now() / 1000,
        username: this.uuid,
        text: message
      };
      var publishConfig = {
        sendByPost: true,
        channel: "controlreply",
        message: m
      };

      pubnub.publish(publishConfig, function(status, response) {
        if (status.error) {
          console.log(status);
        } else {
          // console.log("control message Published w/ timetoken", JSON.stringify(publishConfig));
        }
      });
    },

    onNewMessage(message) {
      var that = this;
      if (message.length < 2) {
        return false;
      }
      let m = {
        time: Date.now() / 1000,
        username: this.uuid,
        text: message
      };
      var publishConfig = {
        sendByPost: true,
        channel: "ch1",
        message: m
      };

      if (that.blockpublish != true) {
        pubnub.publish(publishConfig, function(status, response) {
          if (status.error) {
            console.log(status);
          } else {
            // console.log("message Published w/ timetoken", response.timetoken);
          }
        });
        that.getHereNow();
        // that.adjustScroll();
      }
    }
  },
  created() {
    $.LoadingOverlay("show");
    pubnub.subscribe({
      channels: ["ch1", "control"],
      withPresence: false
    });
  },
  updated() {
    debounce(this.getHereNow, 300, true);

    this.adjustScroll();
  },
  beforeDestroy() {
    //pubnub.unsubscribeAll();
    pubnub.unsubscribe({
      channels: ["ch1", "control"]
    });
    var existingListener = {
      message: function() {}
    };

    if (existingListener) pubnub.removeListener(existingListener);
    if (interval) {
      clearInterval(interval);
    }
  },
  async mounted() {
    if (localStorage.blc) {
      if (localStorage.getItem("blc") == "true") {
        this.blockpublish = true;
      }
    }
    if (localStorage.status_msgs) {
      if (localStorage.getItem("status_msgs") == "yes") {
        this.statusbox = true;
      }
    }
    var that = this;
    that.history();

    pubnub.addListener({
      // status: function(statusEvent) {
      //   if (statusEvent.category === "PNConnectedCategory") {
      //     var newState = {
      //       name: this.uuid,
      //       timestamp: Date.now()
      //     };
      //     pubsub.setState({
      //       channels: ["ch1"],
      //       state: newState
      //     });
      //   }
      // },
      message: function(m) {
        // console.log("livechat msg channel received: ", JSON.stringify(m));
        that.adjustScroll();
        if (m.channel == "ch1") {
          if (
            m.message.text.match(/Forum Replied To/g) ||
            m.message.text.match(/Forum Started Post/g)
          ) {
            if (that.postsbox == true) {
              that.text.push(m.message);
            }
          } else if (m.message.text.match(/Forum Status/g)) {
            if (that.statusbox == true) {
              that.text.push(element.entry);
            }
          } else if (that.livebox == true) {
            that.text.push(m.message);
          }
        } else if (m.channel == "control") {
          if (m.message.text.match(/ping/gi)) {
            that.controlMessage(
              "IAMHERE V" +
                that.$store.state.version.toFixed(4) +
                " Blocked: " +
                that.blockpublish
            );
          }
          //   console.log("live chat control", m.channel, m.message);
         ///
         /// deleted internal stuff
      ///
        }
        that.getHereNow();
        //that.text.push(m.message);
        //console.log("newmsg: ", m.message);
      },
      presence: function(presenceEvent) {
        that.psoccupancy = presenceEvent.occupancy;
        //const i = Date.now();
        console.log("listen presence", presenceEvent);
        that.getHereNow();
      }
    });
    this.getHereNow();
    var mythis = this;
    $(function() {
      var input = document.querySelector(".vue-steam-chat__textarea");
      var button = document.querySelector(".vue-steam-chat__button");
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      input.addEventListener("keyup", function(event) {
        if (event.keyCode === 13) {
          event.preventDefault();
          button.click();
        }
      });
      $.LoadingOverlay("hide");
    });
  }
};
</script>
<style scoped>
.box {
  margin: 0px !important;
  height: 500px;
  margin: 20px;
}

.neo-chat-title {
  color: slategray;
  font-size: 1.2em;
  text-align: center;
  margin: 10px;
}

.room {
  margin-bottom: 20px;
}

.ckbox {
  margin-right: 7px;
  background-color: cornflowerblue;
}

.reverseon {
  color: cornflowerblue;
}

.liveon {
  color: cornflowerblue;
}

.ckbox {
  color: cornflowerblue;
}

.nomargin {
  margin: 0px 0px 0px 0px;
}

.leftmargin {
  margin-left: 30px;
}

.toggler {
  margin: 10px 10px 0px 0px;
}

.channelbar {
  margin: 100px 10px 10px 10px;
  padding-bottom: 10px;
}

.bottomfill {
  margin-bottom: 0px;
  padding-top: 20px;
}
</style>

Neo -

If you are actually using the demo keys, you have two things that are different than the key sets you get in your own PubNub Account:

  1. demo keys have a tight publish throttling setting
  2. demo keys have a 30s long poll setting instead of the default 280s
var pubnub = new PubNub({
        subscribeKey: "demo",
        publishKey: "demo",
        ssl: true,
        keepAlive: true,
        // heartbeatInterval: 10,
        presenceTimeout: 300,
        restore: true,
        uuid: vbchat
    });

Minor feedback

  • keepAlive likely as no effect on browser connections so this only makes sense in Node apps and IIRC, it is default enabled.
  • presenceTimeout : 300 is default so that can be removed
  • heartbeatInterval : 10 will cause a presence heartbeat API to happen every 10s, so keep that commented out

Inquiries
debounce(this.getHereNow, 300, false);

  1. How often is debounce called and what does it do?
  2. What does the "300" and the "false" paremeters mean?

Would like to see the app running to investigate further.

Cheers
Craig

1 Like

Hi Craig,

Thanks for your reply here at unix.com .

Regarding debounce() , this is from the npm debounce package (currently showing over 500K weekly downloads - a shout out to that achievement) here:

https://www.npmjs.com/package/debounce

where:

debounce(fn, wait, [ immediate || false ])

But in the case of our beta Vue.js live chat component we are working with; in the Vue.js updated() lifecycle hook:

 updated() {
    debounce(this.getHereNow, 300, false);
  },

When false , the

this.getHereNow()

function is not executed (according to Chrome DevTools and console.log() ) because the updated() lifecycle hook is faster than 300 ms and false sets the function to fire at the trailing edge of the 300ms.

Hence, this line of code, as written does not fire (I assume you noticed that and hence that is why you were asking) because at 300 ms, the function instance does not exist anymore due to the speed of the Vue lifecycle (it's like a gaming loop, if you are familiar with game programming, which I assume you are)..

I was mainly experimenting with it (debouncing in the updated lifecycle hook) and how changes and updates in the Vue lifecycle might be used to effect pubnub HereNow updates. I will remove this line of code soon because, I guess.

Regarding your remark, "Would like to see the app running to investigate further," this is running live here:

https://www.unix.com/usercp/#/livechat

The current version is 0.7602 (bottom right in the page) and if you see an earlier version, best to clear your JS files from the cache and insure you have the latest version; but I am sure you know that being a JS person!

This app needs more tweaking, as I still not running the API as efficiently as it should be, and the presence (hereNow) is not working (in the Vue.js app) as well as I would like. Basically, I want the list of folks viewing live chat to remain current without a page reload and without excess API polling; so I have tried to update this during changes in the app, for example (1) when a message is received, the occupancy and names will update; and I was experimenting with the Vue.js lifecycle to update occupancy and the uuid list when someone interacts with app, but that is not working as well as I would like (the update() - debounc e() ) idea.

Ideally, it seems to be that PubNub should add a configuration parameter to the subscribe() API as follows;

pubnub.subscribe({
      channels: ["ch1", "control"],
      withPresence: true,
     presenceInterval:   10000 // milliseconds
    });

I notice PubNub often (mostly) uses seconds in API configuration parameters in the SDK. I think it would be better and more consistent to change it to milliseconds (in all PubNub API configuration params) like Javascript interval timers, but then again, I am not a great Javascript developer, or even a good one, so I need to be careful what I am recommending.

I am just a "wannabe" great Javascript developer and "wannabe" expert on PubNub Javascript SDK so I can use PubNub in more sophisticated cybersecurity applications. This live chat app is just getting my feet wet and is not the end goal. It is just a humble beginning.

Update:

UserCP Screeching Frog 0.7603

Still working on tweaking the list of users in live chat.

  • Added new code in update() hook for testing.
updated() {
    this.updateCount++;        // initially set to zero in data()
    if (this.updateCount % 5 === true || this.updateCount == 2) {
      this.getHereNow();
    }
    //console.log("update count", this.updateCount);
  },

It is interesting to console.log() the updateCount var.

For this page, after the initial page mount, updateCount == 2 , so I set the logic to call this.getHereNow(); (again) testing, as it was also called when the page was first mounted (so this is a test, not final answer).

Then I noticed if we receive a new pubnub message the count is incremented by around 5. So for fun, I set this.updateCount % 5 == true but this is really just overkill, because I also call the same method when a message is received. so if I go with this kind of approach, using the view update() lifecycle hook, then I can get rid of the calls to the same method in other parts of the code and control the logic from update().

The problem I am trying to solve is that sometimes when a user enters live chat (that also includes "self"), their occupancy and name (uuid) does not appear correctly above the message window. As soon as a message is processed, all is updated because I call getHereNow() when a message is received.

Of course, if I set pubnub.subscribe() presence TRUE , this problem can be easily solved, but then the API pings the pubnub network every second or so, flooding the network with presence pings from the browser. This is not scaleable.

Hence, the best way forward, in my view, is for PubNub to include a new configuration timer in the pubnub.subscribe() so we can control the frequency of the pubnub presence pings (polling) in the browser.

Or maybe I am just missing some more obvious way in the PubNub SDK to do this more correctly?!

Neo -

If I understand, you are calling hereNow every 300ms , which is a lot.
Assuming that is the case, you should only call hereNow upon successful subscribe to a channel (in the status callback for PNConnectedCategory ).

When you subscribe to a channel(s), subscribe withPresence:true to get the realtime updates for others' join, leave, timeout and state-change.

Not sure what the presenceInterval property would do in the subscribe. I think you are assuming that presence is polling for changes. But it is actually receiving new presence events (in the addListener 's presence callback) just like you receive published messages in the message callback. withPresence:true actually just adds an additional channel for each channel you subscribe to. If you subscribe to channel abc then withPresence:true will also add channel abc-pnpres .

Hope that provides some insights.

Cheers
Craig

No Craig,

We are not calling hereNow every 300 ms.

LOL The function in updated() does not even fire, as I described. I don't think you fully understand how Vue.js lifecycle hooks or the JavaScript debounce() function works.

The u pdated() lifecycle hook in Vue.js only fires when there is a Vue.js event. In this code, that means it on fires on the events I described above.

Even when update() fires, the debounce() function does not fire because it fires only on the trailing edge of 300 ms and that function does not exist (the instance) because the update() lifecycle hook has already exited.

As I said above, that line of code does NOTHING. And in the current version running, it is commented out, since it does nothing.

Regarding PUBNUB.

Frankly, I feel in your replies that you may not fully understand your own API. Perhaps you have not actually every analyzed the XHR calls in DevTools looking at the code under various conditions? It seems you are advsing me from theory (from reading the API docs), not practice (coding and doing the analysis of the XHR calls).

Your API polls presence interval is based on subscribe() (presence set to TRUE). That is not the same as from the pubnub listener. It does not set the presence polling interval.

Yes, the listener listens but the polling is fired every second based in the client when presence is set to TRUE in subscribe.

I have already analyzed these API calls in Chrome Dev tools and it's easy to see what is going on,

You seem to not understand that your API polls presence every second (when it is set in subscribe to TRUE) and there is no way to set that interval in your API.

This generates 100s of thousands of useless transactions because every subscriber is polling the pubnub network every second or so when the subscribe presence TRUE.

I have been over this very carefully in Chome DevTools watching how each flag effects the XHR calls.

Then, again, I could be wrong, LOL... but I have been over this very carefully in Chrome DevTools many times for hours.

Neo -

I just ran the chat app (should have done that first) and I see what is causing the every 10s transaction. Your Presence add-on has a Presence Interval of 10s (that is the default) and you set your Announce Max property to 1, that's 1 channel occupant (20 is the default). So PubNub is sending an update every 10s.

Announce Max
Once the number of occupants reaches this number, join, leave and timeout events are no longer sent as realtime presence events to those listening ( subscribed withPresence:true ). Instead, PubNub Network will send an occupancy count every 10s (or whatever the value is for the Interval property). If you want to know who join, leave, timeout since the last interval update, you need to enable Presence Deltas.

My recommendation: set Announce Max back to 20. A different number may be better, but that should suffice for now. Rarely is this value ever changed for most apps. I took the liberty of updating this property to 20 to prevent your account from accumulating needless transactions. They were not benefitting your app in the slightest anyways. You would need to enable Presence Deltas for that Announce Max = 1 setting to be useful and then you would have to handle the presence delta event payload in your client app, which I don't think you are doing at the moment. You can set this back to 1 if you feel that is what you need.

Let's schedule time for a PubNub overview session and I can give you all the ins and outs, gotchas, best practices and how it works under the covers so you can make the best informed decisions with your implementation. I know I can save you hours of frustration and you'll appreciate how this all works much more.

Cheers
Craig

--- Post updated at 03:32 PM ---

Tim - I understand our APIs quite well. Your app, not so much, admittedly. Not a Vue expert. See my last post.

1 Like

Thanks Craig,

I'm glad you understand your API very well.

I was beginning to wonder with your first reply today, when you did not understand how the Vue.js lifecycle hooks work or what happens with debounce() when it fires on the trailing edge of an interval in a Vue lifecycle hook.

Regarding the PubNub API, thanks for assuring me you understand it.

I still think PubNub needs a new configuration parameter in subscribe() which sets the polling interval for presence, but let me take another look tomorrow since you have made some changes in our configuration and see if I set subscribe presence to TRUE in my code (it is current set to FALSE), if it keeps polling and polling like crazy.

I am sure I do not understand your API as well as you, but I do make subtle changes in the code and watch the XHR effects and what is going on in Chrome DevTools, in great detail :slight_smile:

I don't use Vue.js and I have never heard of the debounce API. Neither is specific PubNub and there are dozens of JS frameworks out there so not possible, or necessary, to know them all.

I still think PubNub needs a new configuration parameter in subscribe() which sets the polling interval for presence, but let me take another look tomorrow since you have made some changes in our configuration and see if I set subscribe presence to TRUE in my code (it is current set to FALSE), if it keeps polling and polling like crazy.

Subscribe already has an interval, it is the subscribeTimeout config, that you can set in the PubNub init, but please do not. It should only be changed for very specific reasons, that I will not get into right now. You will only cause your app to have increased transactions at no additional upside or cause your app to break depending on whether you set it shorter or longer. The default subscribeTimeout is 280s, and this is exactly the same for listening to presence events because it is on the subscribe connection. The hereNow is a completely different API and a different connection which does not work on the subscribe connection.

When you subscribe withPresence:true, you are just adding presence channels (with the -pnpres suffix) to the list of subscribed channels. When another person subscribes to that channel, those listening to presence events (withPresence:true) on that channel will receive that presence event, in realtime, not on some polling cycle.

But see my response about Announce Max. The increased transaction were self-inflicted :wink: Not that you would have known the side effects but rarely does anyone change that setting.

And I just checked the chat app again. It appears you upgraded to lastest PubNub JS SDK version - cheers - and I see that the client is not having the 10s interval response anymore. At most it will be 280s or everytime someone joins, leaves, timeouts, or a message is published and therefore received.

Cheers
Craig

1 Like

Hey Craig,

i have been using the latest Node.js SDK since day one and have not ungraded since my initial node.js install.

You "lost me" on your "upgrade" comment.

Was there a new version released in the last week or so?

I have only used one SDK version, the initial version I originally installed.

When I was watching the network requests earlier today, I thought I saw a reference to PubNub JavaScript v4.20.? and then later saw 4.23.0. I check the server logs and see that wasn't the case, my mistake.

Interestingly enough, I did see 91 requests that came from JS v3.16.3.

1 Like

Hi Craig,

Here is the pubnub dependency in the node project:

 "pubnub": "^4.23.0",

The PHP version of the SDK (used for server side messages) was provided to me by your team, and of course that it is a different project and I don't have that information handy.

I have no idea why the pubnode SDK has tokens that shows JS v3.16.3 , but I just started this project a few weeks ago and only installed "pubnub": "^4.23.0",

I assume there is legacy code in your SDK and that is why you are setting what you are seeing.

I did install "pubnub-vue": "^1.0.1", initially, but I abandoned that code rather quickly. I probably should remove the that unused package from my project file, which I just did:

npm uninstall pubnub-vue --save

and rebuilt the project without this unused code in version 0.7604, which I just built and installed on the server.

Maybe removing that pubnub-vue repo, which I thought was unused, will get rid of the JS v3.16.3 code you were seeing?

Also, when I have time, I'll rebuild the project and test:

  pubnub.subscribe({
      channels: ["ch1", "control"],
      withPresence: true
    });

right now, it is still running:

  pubnub.subscribe({
      channels: ["ch1", "control"],
      withPresence: false
    });

I will test this again soon, with the changes you made to our pubnub config and report back!

Thanks for all your great help and interest in this little project

UserCP Screeching Frog 0.7605

pubnubcraig's changes on the backend have really helped out.

Thanks Craig!!

  • Set pubnub.subscribe( withPresence: true });
  • Removed all getHereNow() calls except on the initial page created() , as Craig suggested (demanded :slight_smile: )
  • Wrote new code in the Listener() in the presence object.

It seems to work as expected now, with the exception of sometimes the user does not see "himself" as a participant in the live chat. I need to figure that small bug out.

Here is the core version v0.7605 Vue.js code:

<template>
  <div class="bottomfill">
    <div class="d-flex justify-content-start flex-wrap room nomargin togglearea">
      <div class="checkbox leftmargin">
        <label>
          <input class="ckbox" type="checkbox" v-model="reverseorder">
          <span v-bind:class="{reverseon: this.reverseorder}" class="toggler">Toggle Message Order</span>
          <span v-if="this.reverseorder">
            <i v-bind:class="{reverseon: this.reverseorder}" class="tim-icons icon-minimal-down"></i>
          </span>
          <span v-else>
            <i class="tim-icons icon-minimal-up"></i>
          </span>
        </label>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room">
      <div class="col-md-12 box">
        <div
          class="neo-chat-title"
          v-if="this.psoccupancy"
        >Live Chat Participants: {{ this.psoccupancy}}</div>

        <div class="neo-chat-title">{{ this.userlist}}</div>
        <VueSteamChat :messages="this.msglist" @vue-steam-chat-on-message="onNewMessage"/>
      </div>
    </div>

    <div class="d-flex justify-content-around flex-wrap room channelbar">
      <div>Channels</div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="livebox">
          <span v-bind:class="{liveon: this.livebox}">Live</span>
        </label>
      </div>
      <div class="checkbox">
        <label>
          <input class="ckbox" type="checkbox" v-model="postsbox">
          <span v-bind:class="{liveon: this.postsbox}">Posts</span>
        </label>
      </div>
      <div class="checkbox disabled">
        <label>
          <input class="ckbox" type="checkbox" v-model="statusbox">
          <span v-bind:class="{liveon: this.statusbox}">Status</span>
        </label>
      </div>
    </div>
  </div>
</template>

<script>
function init() {
  sessionStorage.setItem("loaded", true);
}
window.onload = init;
var _ = require("lodash");
import Vue from "vue";
import VueSteamChat from "vue-steam-chat";
import { setTimeout, setInterval, clearInterval } from "timers";
var PubNub = require("pubnub");
require("vue-steam-chat/dist/index.css");
var interval = null;
var vbchat = "";

if (window.vbname == "Unregistered") {
  const d = Math.floor(Math.random() * 100);
  if (localStorage.getItem("randomuser")) {
    vbchat = "Guest " + localStorage.getItem("randomuser");
  } else {
    const d = Math.floor(Math.random() * 100);
    localStorage.setItem("randomuser", d);
    vbchat = "Guest " + localStorage.getItem("randomuser");
  }
} else {
  vbchat = window.vbname;
}

var pubnub = new PubNub({
  subscribeKey: "demo",
  publishKey: "demo",
  ssl: true,
  keepAlive: true,
  // heartbeatInterval: 10,
  presenceTimeout: 300,
  restore: true,
  uuid: vbchat
});

export default {
  name: "vue-steam-chat",
  components: {
    VueSteamChat
  },
  computed: {
    msglist() {
      var listarray = [];
      //console.log("this.text ", this.text);
      var that = this;
      this.text.forEach(function(element) {
        if (
          element.text.match(/Forum Replied To/g) ||
          element.text.match(/Forum Started Post/g)
        ) {
          if (that.postsbox == true) {
            listarray.push(element);
          }
        } else if (element.text.match(/Forum Status/g)) {
          if (that.statusbox == true) {
            listarray.push(element);
          }
        } else if (that.livebox == true) {
          listarray.push(element);
        }
      });
      if (this.reverseorder == true) {
        return listarray.reverse();
      } else {
        return listarray;
      }
      //this.adjustScroll();
    },
    uuid() {
      return pubnub.getUUID();
    }
  },
  data() {
    return {
      updateCount: 0,
      blockpublish: false,
      livebox: true,
      reverseorder: true,
      postsbox: true,
      statusbox: true,
      historydone: false,
      userlist: "",
      userarray: [],
      uuida: window.vbuserId,
      uuname: window.vbusername,
      ch1: null,
      time: Date.now() / 1000,
      chathist: [],
      laststatuschange: [{}],
      occupancy: 0,
      psoccupancy: 0,
      initdone: 0,
      status: 0,
      text: [
        {
          time: 1506117496,
          username: "Gaben",
          text: "Chat initialized ..."
        },
        {
          time: 1506117500,
          username: "Solo",
          text: "So, say something !!"
        },
        {
          time: 1506117530,
          username: "Neo",
          text: "Hmmm ..."
        }
      ]
    };
  },
  methods: {
    adjustScroll() {
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      var view = document.querySelector(".bottomfill");

      if (!this.reverseorder) {
        scroll.scrollTop = 0;
        view.scrollIntoView({
          behavior: "smooth",
          block: "start",
          inline: "nearest"
        });
      } else {
        scroll.scrollTop = scroll.scrollHeight;
        channels.scrollIntoView({
          behavior: "smooth",
          block: "end",
          inline: "nearest"
        });
      }
    },
    initScroll() {
        var view = document.querySelector(".bottomfill");
        behavior: "smooth",
        block: "end",
        inline: "nearest"
      });
    },
    getHereNow() {
      var that = this;
      pubnub.hereNow(
        {
          channels: ["ch1"],
          // channelGroups : ["cg1"],
          includeUUIDs: true,
          includeState: true
        },
        function(status, response) {
          if (response.totalOccupancy == 0) {
            that.psoccupancy = 1;
            var allusers = "";
          } else {
            that.psoccupancy = response.totalOccupancy;
            var allusers = "";
          }
          // console.log("herenow: ", response);

          var index = 0;

          response.channels.ch1.occupants.forEach(function(user) {
            if (response.channels.ch1.occupants) {
              that.userarray.push(user);
              if (index == 0) allusers = user.uuid;
              else allusers = allusers + ", " + user.uuid;
            }
            index++;
            that.userlist = allusers;
          });
        }
      );
    },
    history() {
      var that = this;
      if (pubnub) {
        pubnub.history(
          {
            reverse: false,
            channel: "ch1",
            count: 100, // how many items to fetch
            stringifiedTimeToken: true // false is the default
          },
          function(status, response) {
            if (status.error == false) {
              response.messages.forEach(function(element) {
                if (element.entry.username && element.entry.text != undefined) {
                  if (
                    element.entry.text.match(/Forum Replied To/g) ||
                    element.entry.text.match(/Forum Started Post/g)
                  ) {
                    if (that.postsbox == true) {
                      that.text.push(element.entry);
                    }
                  } else if (element.entry.text.match(/Forum Status/g)) {
                    if (that.statusbox == true) {
                      that.text.push(element.entry);
                    }
                  } else if (that.livebox == true) {
                    that.text.push(element.entry);
                  }
                }
              });
              that.historydone = true;
            } else {
              console.log("error ", status);
            }
          }
        );
      }
      that.initScroll();
    },
    controlMessage(message) {
      var that = this;
      if (message.length < 2) {
        return false;
      }
      let m = {
        time: Date.now() / 1000,
        username: this.uuid,
        text: message
      };
      var publishConfig = {
        sendByPost: true,
        channel: "controlreply",
        message: m
      };

      pubnub.publish(publishConfig, function(status, response) {
        if (status.error) {
          console.log(status);
        } else {
          // console.log("control message Published w/ timetoken", JSON.stringify(publishConfig));
        }
      });
    },

    onNewMessage(message) {
      var that = this;
      if (message.length < 2) {
        return false;
      }
      let m = {
        time: Date.now() / 1000,
        username: this.uuid,
        text: message
      };
      var publishConfig = {
        sendByPost: true,
        channel: "ch1",
        message: m
      };

      if (that.blockpublish != true) {
        pubnub.publish(publishConfig, function(status, response) {
          if (status.error) {
            console.log(status);
          } else {
            // console.log("message Published w/ timetoken", response.timetoken);
          }
        });
      }
    }
  },
  created() {
    $.LoadingOverlay("show");
    pubnub.subscribe({
      channels: ["ch1", "control"],
      withPresence: true
    });
  },
  updated() {
    this.adjustScroll();
  },
  beforeDestroy() {
    pubnub.unsubscribe({
      channels: ["ch1", "control"]
    });
    var existingListener = {
      message: function() {}
    };

    if (existingListener) pubnub.removeListener(existingListener);
    if (interval) {
      clearInterval(interval);
    }
  },
  async mounted() {
    if (localStorage.blc) {
      if (localStorage.getItem("blc") == "true") {
        this.blockpublish = true;
      }
    }
    if (localStorage.status_msgs) {
      if (localStorage.getItem("status_msgs") == "yes") {
        this.statusbox = true;
      }
    }
    var that = this;
    that.history();
    that.getHereNow();
    pubnub.addListener({
  
      message: function(m) {
        // console.log("livechat msg channel received: ", JSON.stringify(m));
        that.adjustScroll();
        if (m.channel == "ch1") {
          if (
            m.message.text.match(/Forum Replied To/g) ||
            m.message.text.match(/Forum Started Post/g)
          ) {
            if (that.postsbox == true) {
              that.text.push(m.message);
            }
          } else if (m.message.text.match(/Forum Status/g)) {
            if (that.statusbox == true) {
              that.text.push(element.entry);
            }
          } else if (that.livebox == true) {
            that.text.push(m.message);
          }
        } else if (m.channel == "control") {
          if (m.message.text.match(/ping/gi)) {
            that.controlMessage(
              "IAMHERE V" +
                that.$store.state.version.toFixed(4) +
                " Blocked: " +
                that.blockpublish
            );
          }
          //   console.log("live chat control", m.channel, m.message);
         //
         // removed internal control code
        //
      }
      },
      presence: function(presenceEvent) {
        that.psoccupancy = presenceEvent.occupancy;
        //const i = Date.now();
        //console.log("listen presence", presenceEvent);
        if (presenceEvent.channel == "ch1") {
          if (presenceEvent.action == "join") {
            var rawarray = that.userlist.split(",");
            var arr = _.remove(rawarray, function(n) {
              return n.length > 2;
            });
            arr.push(presenceEvent.uuid);
            that.userlist = arr.join(", ");
            //that.userlist.replace(/^,/, "");
            //console.log("listen presence join", that.userlist);
          } else if (presenceEvent.action == "leave") {
            var arr = that.userlist.split(",");
            var trimmed = [];
            arr.forEach(element => {
              trimmed.push(element.trim());
            });

            var removed = _.remove(trimmed, function(n) {
              return n != presenceEvent.uuid.trim();
            });
            var newarr = removed.join(",");
            that.userlist = newarr;
            // console.log("listen presence leave", newarr);
          }
        }
      }
    });
    var mythis = this;
    $(function() {
      var input = document.querySelector(".vue-steam-chat__textarea");
      var button = document.querySelector(".vue-steam-chat__button");
      var scroll = document.querySelector(".vue-steam-chat__wrapper--scroll");
      input.addEventListener("keyup", function(event) {
        if (event.keyCode === 13) {
          event.preventDefault();
          button.click();
        }
      });
      $.LoadingOverlay("hide");
    });
  }
};
</script>
<style scoped>
.box {
  margin: 0px !important;
  height: 500px;
  margin: 20px;
}

.neo-chat-title {
  color: slategray;
  font-size: 1.2em;
  text-align: center;
  margin: 10px;
}

.room {
  margin-bottom: 20px;
}

.ckbox {
  margin-right: 7px;
  background-color: cornflowerblue;
}

.reverseon,
.liveon,
.ckbox {
  color: cornflowerblue;
}

.nomargin {
  margin: 0px 0px 0px 0px;
}

.leftmargin {
  margin-left: 30px;
}

.toggler {
  margin: 10px 10px 0px 0px;
}

.channelbar {
  margin: 100px 10px 10px 10px;
  padding-bottom: 10px;
}

.bottomfill {
  margin-bottom: 0px;
  padding-top: 20px;
}
</style>

Mystery solved - I dug in further to the logs to find out the source of the v3 SDK. I was our Realtime Analytics tool that you must have enabled at one time. It hasn't been upgraded because it still works on that older version, but likely we will be EOL'ing/removing that tool in the next release of the PubNub Admin Dashboard.

1 Like

Well, considering PubNub supported countless SDKs and features, it's hard to keep track of all the changes in a face paced world!

Thanks for following up, Craig.

PubNub is great stuff and slowing I'm learning more and more how the SDK works; but I have a long way to go.