• Login
    • Search
    • Categories
    • Recent
    • Tags
    • Users
    • Groups
    • Rules
    • Help

    Do more on the web, with a fast and secure browser!

    Download Opera browser with:

    • built-in ad blocker
    • battery saver
    • free VPN
    Download Opera

    Twitch sidebar button doesn't work at all!

    Opera GX
    error message bug report twitch
    3
    4
    3230
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • A Former User
      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 ❤

      Annotation 2019-06-12 114724.png
      Opera Snapshot_2019-06-12_115109_extensions.png

      "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();
      

      Opera Snapshot_2019-06-12_115030_extensions.png

      Reply Quote 0
        1 Reply Last reply
      • A Former User
        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...

        Reply Quote 0
          A Former User 1 Reply Last reply
        • A Former User
          A Former User @Guest 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.

          Reply Quote 0
            1 Reply Last reply
          • A Former User
            A Former User last edited by

            The Twitch sidebar is not working properly, it doesn't show any channel.912757f789061f9ce2e498e7006999e5.png

            Reply Quote 0
              1 Reply Last reply
            • First post
              Last post

            Computer browsers

            • Opera for Windows
            • Opera for Mac
            • Opera for Linux
            • Opera beta version
            • Opera USB

            Mobile browsers

            • Opera for Android
            • Opera Mini
            • Opera Touch
            • Opera for basic phones

            • Add-ons
            • Opera account
            • Wallpapers
            • Opera Ads

            • Help & support
            • Opera blogs
            • Opera forums
            • Dev.Opera

            • Security
            • Privacy
            • Cookies Policy
            • EULA
            • Terms of Service

            • About Opera
            • Press info
            • Jobs
            • Investors
            • Become a partner
            • Contact us

            Follow Opera

            • Opera - Facebook
            • Opera - Twitter
            • Opera - YouTube
            • Opera - LinkedIn
            • Opera - Instagram

            © Opera Software 1995-