Twitch sidebar button doesn't work at all!
-
A Former User last edited by
I'm following 137 streamer, and there's 0 icons, also tried to make it run as admin by default, but still the same bug, I can't actually describe the bug 100% so I will let the images/errors talk! I hope to consider fix it asap
"full error code"
/** * Copyright (C) 2019 Opera Software AS. All rights reserved. * This file is an original work developed by Opera Software AS */ import {TwitchAPI} from '/tools/twitch_api.js'; import {Colors} from './tools/colors.js'; import {StatsReporter} from './tools/stats.js'; const CLIENT_ID = 'ju0ntw6bpd1i0cx1ama5buw1q377qy'; const REDIR_URL_STR = `https://${chrome.runtime.id}.chromiumapp.org/`; const REDIR_URL = new URL(REDIR_URL_STR); // maybe id_token not needed? const RESPONSE_TYPE = 'token+id_token'; const SCOPE = 'openid'; const AUTH_URL = `https://id.twitch.tv/oauth2/authorize?client_id=${CLIENT_ID}&` + `redirect_uri=${REDIR_URL_STR}&response_type=${RESPONSE_TYPE}&` + `scope=${SCOPE}`; const REDIR_TOKEN_REGEXP = /access_token=(\w+)/; const STATE_REGEXP = /state=(\w+)/; // TODO decide on poll interval const RERESH_INTERVAL_SECONDS = 60; const CONTEXT_MENU_ID_LOGOUT = 'logout'; const CONTEXT_MENU_ID_MUTE = 'mute'; const CONTEXT_MENU_ID_UNMUTE = 'unmute'; class Sounds { constructor() { this.audio = new Audio('assets/notification.mp3'); } play() { if (!this.isMuted()) { this.audio.play(); } } setMuted(muted) { localStorage['muted'] = !!muted; } isMuted() { return localStorage['muted'] === 'true'; } } class TwitchApp { constructor() { this.color = new Colors(); this.stats = new StatsReporter('gx', 'twitch'); this.setupConnections(); this.sounds = new Sounds(); this.twitchAPI = new TwitchAPI(localStorage.accessToken, CLIENT_ID); this.initContextMenu(); if (this.needsAuthentication()) { this.waitForAuthentication(); } else { this.init(); } } onContextMenuCommand(info) { switch (info.menuItemId) { case CONTEXT_MENU_ID_LOGOUT: this.logout(); break; case CONTEXT_MENU_ID_MUTE: this.sounds.setMuted(true); this.updateContextMenu(); break; case CONTEXT_MENU_ID_UNMUTE: this.sounds.setMuted(false); this.updateContextMenu(); break; default: break; } } get followsLocal() { try { return JSON.parse(localStorage['follows']); } catch (e) { return []; } } set followsLocal(value) { let stringified = JSON.stringify(value); if (stringified !== localStorage['follows']) { localStorage['follows'] = stringified; this.notifyUpdateNeeded(); this.updateBadge(value); } } initContextMenu() { chrome.contextMenus.removeAll(); const logoutItem = { id: CONTEXT_MENU_ID_LOGOUT, title: chrome.i18n.getMessage('contextMenuLogout'), visible: true, contexts: ['sidebar_action', 'browser_action'], enabled: !this.needsAuthentication(), }; chrome.contextMenus.create(logoutItem, evt => {}); const muteItem = { id: CONTEXT_MENU_ID_MUTE, title: chrome.i18n.getMessage('mute'), visible: !this.sounds.isMuted(), contexts: ['sidebar_action', 'browser_action'], }; chrome.contextMenus.create(muteItem, evt => {}); const unmuteItem = { id: CONTEXT_MENU_ID_UNMUTE, title: chrome.i18n.getMessage('unmute'), visible: this.sounds.isMuted(), contexts: ['sidebar_action', 'browser_action'], }; chrome.contextMenus.create(unmuteItem, evt => {}); chrome.contextMenus.onClicked.addListener( this.onContextMenuCommand.bind(this) ); } setBadge(text) { opr.sidebarAction.setBadgeText({text}); } clearBadge() { opr.sidebarAction.setBadgeText({text: ''}); } updateContextMenu() { chrome.contextMenus.update( CONTEXT_MENU_ID_LOGOUT, {enabled: !this.needsAuthentication()}, evt => {} ); chrome.contextMenus.update( CONTEXT_MENU_ID_MUTE, {visible: !this.sounds.isMuted()}, evt => {} ); chrome.contextMenus.update( CONTEXT_MENU_ID_UNMUTE, {visible: this.sounds.isMuted()}, evt => {} ); } async twitchRequest(func, params = {}) { if (this.needsAuthentication() && params.needsAuth !== false) { return this._getNeedsAuthData(); } try { return await func(); } catch (err) { if (err.status === 401) { delete localStorage.accessToken; return this._getNeedsAuthData(); } return {error: 'unexpected_error'}; } } setupConnections() { this.ports = []; chrome.runtime.onConnect.addListener(port => { this.ports.push(port); port.onMessage.addListener(msg => this._onMessage(port, msg)); port.onDisconnect.addListener(port => { let index = this.ports.indexOf(port); if (index >= 0) { this.ports.splice(index, 1); } }); }); } async setupUpdates() { // First update should always update the badge. const follows = await this.updateStreamsInfo(); this.updateBadge(follows); window.setInterval(() => { this.updateStreamsInfo(); }, 1000 * RERESH_INTERVAL_SECONDS); } async notifyUpdateNeeded() { for (let port of this.ports) { port.postMessage({updateNeeded: true}); } } updateBadge(follows) { let liveCount = 0; for (let follow of follows) { if (follow.isLive) { ++liveCount; } } // When liveCount = 0, don't show badge nor play sound if (liveCount === 0) { this.color.setBadgeInactive(); } else { this.color.setBadgeActive(); } this.setBadge(this.capLiveCount(liveCount)); } init() { this.twitchAPI = new TwitchAPI(localStorage.accessToken, CLIENT_ID); this.updateContextMenu(); this.setupUpdates(); this.color.setBadgeInactive(); } getStateString() { if (!this.authStateString) { const STATE_LENTH = 16; let array = new Uint8Array(STATE_LENTH); window.crypto.getRandomValues(array); // hex encoded this.authStateString = Array.prototype.map .call(array, x => `00${x.toString(16)}`.slice(-2)) .join(''); } return this.authStateString; } getAuthUrl() { return `${AUTH_URL}&state=${this.getStateString()}`; } needsAuthentication() { return !localStorage.accessToken; } parseUrl(url) { try { return new URL(url); } catch (e) { return null; } } isRedirURL(parsedUrl) { return ( parsedUrl && parsedUrl.origin === REDIR_URL.origin && parsedUrl.path === REDIR_URL.path ); } // Returns the token if correct, null otherwise getTokenFromRedirectUrl(url) { let parsedUrl = this.parseUrl(url); if (!this.isRedirURL(parsedUrl)) { return null; } let stateMatch = parsedUrl.hash.match(STATE_REGEXP); if (stateMatch.length !== 2 || stateMatch[1] !== this.getStateString()) { return null; } let tokenMatch = parsedUrl.hash.match(REDIR_TOKEN_REGEXP); if (tokenMatch.length === 2) { return tokenMatch[1]; } return null; } login() { if (this._loginPromise !== undefined) { return this._loginPromise; } const authInfo = { url: this.getAuthUrl(), interactive: true, }; this._loginPromise = new Promise(resolve => { chrome.identity.launchWebAuthFlow(authInfo, url => { const token = this.getTokenFromRedirectUrl(url); if (token === null) { resolve(false); } else { localStorage.accessToken = token; this.init(); this.stats.recordBoolean('LoggedIn', true); resolve(true); } this._loginPromise = undefined; }); }); return this._loginPromise; } waitForAuthentication() {} async _onMessage(port, msg) { if (msg.id === undefined) { // ERROR } switch (msg.command) { case 'getStreamsInfo': { const data = await this.twitchRequest(this.getStreamsInfo.bind(this)); port.postMessage({isReply: true, id: msg.id, data: data}); break; } case 'getUserInfo': { const data = await this.twitchRequest(this.getUserInfo.bind(this)); port.postMessage({isReply: true, id: msg.id, data: data}); break; } case 'getTopStreamers': { const data = await this.twitchRequest(this.getTopStreams.bind(this), { needsAuth: false, }); port.postMessage({isReply: true, id: msg.id, data: data}); break; } case 'login': { let success = await this.login(); port.postMessage({isReply: true, id: msg.id, data: {success: success}}); break; } case 'logout': { await this.logout(); this.stats.recordBoolean('LoggedIn', false); port.postMessage({isReply: true, id: msg.id, data: {}}); break; } } } async logout() { await this.twitchAPI.logout(); delete localStorage.accessToken; this.clearBadge(); chrome.runtime.reload(); return; } capLiveCount(liveCount) { return liveCount > 99 ? `${liveCount}+` : String(liveCount); } updateStreamsInfo() { return this.twitchRequest(async () => { let api = this.twitchAPI; const userInfo = await api.getUserInfo(); const followedChannels = await api.getFollowedChannels( userInfo.data[0].id ); const follows = await Promise.all( followedChannels.data.map(async follow => { const [user, streams] = await Promise.all([ api.getUserInfo(follow.to_id), api.getStreams(follow.to_id), ]); let isLive = false; if (streams.data.length > 0) { isLive = true; } return { id: follow.to_id, name: user.data[0].display_name, iconUrl: user.data[0].profile_image_url, followed_at: user.data[0].followed_at, isLive, }; }) ); const sortedFollows = follows.sort((a, b) => a.followed_at >= b.followed_at ? 1 : -1 ); const oldFollows = this.followsLocal; const oldIds = new Set(oldFollows.map(follow => follow.id)); this.followsLocal = sortedFollows; if (!sortedFollows.find(follow => oldIds.has(follow.id))) { this.sounds.play(); } return sortedFollows; }); } async getUserInfo() { const userResults = await this.twitchAPI.getUserInfo(); const user = userResults.data[0]; const followersResults = await this.twitchAPI.getFollowersChannels(user.id); user.followers = followersResults.total || 0; return user; } toStreamsInfo(follows) { return { channels: follows, }; } _getNeedsAuthData() { return { needsAuthentication: true, }; } async getStreamsInfo() { let follows = this.followsLocal; if (Object.keys(follows).length === 0) { await this.updateStreamsInfo(); follows = this.followsLocal; } return this.toStreamsInfo(follows); } } window.twitch = new TwitchApp();
-
A Former User last edited by
Had the same issue. I thought a reinstall of the extention might be a solution but I can't find that one at all...
-
A Former User last edited by
@earthplague Yeah I thought that too, but I didn't uninstall it, I hope that code helps the devs to solve it.
-
A Former User last edited by
The Twitch sidebar is not working properly, it doesn't show any channel.