Skip to content

Commit

Permalink
feat: Twitch auto reconnect and status (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
skarab42 authored Jan 27, 2021
1 parent 55e7a17 commit 797da5c
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 16 deletions.
26 changes: 24 additions & 2 deletions app/server/libs/twitch/chat/connect.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
const state = require("../state");
const { chat } = require("../index");

module.exports = function connect() {
let connectId = null;
const reconnectDelay = 5; // seconds

function delayConnect() {
connectId && clearTimeout(connectId);
connectId = setTimeout(reconnect, reconnectDelay * 1000);
}

function reconnect() {
state.set("chat.connected", false);
connect().catch(() => {
delayConnect();
});
}

chat.onDisconnect((manually) => {
state.set("chat.connected", false);
if (!manually) delayConnect();
});

function connect() {
if (state.get("chat.connected")) {
return Promise.resolve({ alreadyConnected: true });
}
Expand All @@ -28,4 +48,6 @@ module.exports = function connect() {
state.set("chat.connected", false);
return Promise.reject(error);
});
};
}

module.exports = connect;
36 changes: 25 additions & 11 deletions app/server/libs/twitch/pubsub/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,43 @@ const { BasicPubSubClient, PubSubClient } = require("twitch-pubsub-client");
const onRedemption = require("./onRedemption");
const onBits = require("./onBits");
const twitch = require("../index");
const state = require("../state");

const reconnectDelay = 5;
let connectId = null;
const reconnectDelay = 5; // seconds

const rootClient = new BasicPubSubClient();
const pubSubClient = new PubSubClient(rootClient);

function delayConnect() {
connectId && clearTimeout(connectId);
connectId = setTimeout(reconnect, reconnectDelay * 1000);
}

function reconnect() {
console.log(`Reconnecting...`);
pubSubClient._rootClient.reconnect();
state.set("pubsub.connected", false);
rootClient
.reconnect()
.then(() => {
state.set("pubsub.connected", true);
})
.catch(() => {
delayConnect();
});
}

rootClient.onDisconnect((manually) => {
state.set("pubsub.connected", false);
if (!manually) delayConnect();
});

module.exports = async function init() {
state.set("pubsub.connected", false);

const userId = await pubSubClient.registerUserListener(twitch.api);

await pubSubClient.onRedemption(userId, onRedemption);
await pubSubClient.onBits(userId, onBits);

pubSubClient._rootClient.onDisconnect((manually, reason) => {
// if (manually) return;
console.log("PUBSUB ERROR >>>", reason);
console.log(`Reconnecting in ${reconnectDelay} sec.`);
setTimeout(reconnect, reconnectDelay * 1000);
});

setTimeout(() => pubSubClient._rootClient.disconnect(), 5000);
state.set("pubsub.connected", true);
};
3 changes: 3 additions & 0 deletions app/server/libs/twitch/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ let state = {
registered: false,
joinedChannels: [],
},
pubsub: {
connected: false,
},
};

function get(key = null, defaultValue = null) {
Expand Down
36 changes: 33 additions & 3 deletions front-src/client/components/Twitch/Login.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script>
import api from "@/api/twitch";
import { _ } from "@/libs/i18next";
import { user } from "@/stores/twitch";
import StatusLine from "./StatusLine.svelte";
import StatusSpinner from "./StatusSpinner.svelte";
import { user, chat, pubsub } from "@/stores/twitch";
import TwitchIcon from "@/assets/images/Twitch_icon.svg";
function onLogin() {
Expand All @@ -10,15 +12,43 @@
console.log("error:", error);
});
}
let status = false;
function showStatus() {
status = true;
}
function hideStatus() {
status = false;
}
$: connected = $chat.connected && $pubsub.connected;
</script>

{#if $user}
<div class="flex px-2 py-1 items-center" on:click="{onLogin}">
<div
class="relative flex px-2 flex-shrink-0 py-1 items-center justify-center"
on:click="{onLogin}"
on:mouseenter="{showStatus}"
on:mouseleave="{hideStatus}"
>
<StatusSpinner connected="{connected}" />
<img
class="w-8 h-8 rounded-full"
class="z-10 w-8 h-8 rounded-full"
src="{$user.profile_image_url}"
alt="{$user.display_name}"
/>
{#if status}
<div
on:mouseleave|stopPropagation
class="absolute p-2 whitespace-nowrap text-gray-800 bg-gray-300 rounded"
style="top:40px; right:0px; min-width:100px; z-index:500;"
>
<StatusLine label="Chat" status="{$chat}" />
<StatusLine label="PubSub" status="{$pubsub}" />
</div>
{/if}
</div>
{:else}
<div
Expand Down
16 changes: 16 additions & 0 deletions front-src/client/components/Twitch/StatusLine.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script>
import Icon from "@/components/UI/Icon.svelte";
import MdCheck from "svelte-icons/md/MdCheck.svelte";
import MdError from "svelte-icons/md/MdError.svelte";
export let label;
export let status;
$: statusIcon = status.connected ? MdCheck : MdError;
$: statusColor = status.connected ? "text-green-600" : "text-red-600";
</script>

<div class="px-2 flex gap-2 {statusColor}">
<span class="flex-auto">{label}</span>
<Icon icon="{statusIcon}" />
</div>
28 changes: 28 additions & 0 deletions front-src/client/components/Twitch/StatusSpinner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script>
export let connected;
$: color = connected ? "green" : "red";
</script>

<style>
.spin {
animation: spin 2s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

{#if !connected}
<div
class="spin absolute rounded-full"
style="width:2.2rem; height:2.2rem; background: linear-gradient(90deg, {color}, transparent 35%);"
></div>
<div class="spin absolute bg-primary-darker w-8 h-8 rounded-full"></div>
{/if}
3 changes: 3 additions & 0 deletions front-src/client/stores/twitch.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const store = writable(null);
export const stream = writable(null);
export const user = writable(null);
export const chat = writable(null);
export const pubsub = writable(null);
export const commands = writable([]);
export const rewards = writable([]);
export const followers = writable([]);
Expand All @@ -15,6 +16,7 @@ async function loadOnce() {
if (loaded) return;

api.on("state.stream", stream.set);
api.on("state.pubsub", pubsub.set);
api.on("state.user", user.set);
api.on("state.chat", chat.set);

Expand Down Expand Up @@ -47,6 +49,7 @@ export default async function load() {
const state = await api.getState();

stream.set(state.stream);
pubsub.set(state.pubsub);
user.set(state.user);
chat.set(state.chat);

Expand Down

0 comments on commit 797da5c

Please sign in to comment.