+
Feedback
-
-
- Please keep in mind that FrankerFaceZ v4 is under heavy development.
-
-
-
- Okay, still here? Great! You can provide feedback and bug reports by
+ You can provide feedback and bug reports by
opening an issue at our GitHub repository.
@@ -21,6 +15,11 @@
When creating a GitHub issue, please check that someone else hasn't
already created one for what you'd like to discuss or report.
+
+
+ When creating an issue, please also upload the following logs and
+ include a link in your report.
+
diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js
index 23ce2acd..fa138244 100644
--- a/src/modules/main_menu/index.js
+++ b/src/modules/main_menu/index.js
@@ -57,6 +57,16 @@ export default class MainMenu extends Module {
component: 'feedback-page'
});
+ this.settings.addUI('feedback.log', {
+ path: 'Home > Feedback >> Log @{"sort": 1000}',
+ component: 'async-text',
+ watch: [
+ 'reports.error.include-user',
+ 'reports.error.include-settings'
+ ],
+ data: () => this.resolve('core').generateLog()
+ })
+
this.settings.addUI('changelog', {
path: 'Home > Changelog',
component: 'changelog'
diff --git a/src/modules/metadata.jsx b/src/modules/metadata.jsx
index a928df7d..97e8fefd 100644
--- a/src/modules/metadata.jsx
+++ b/src/modules/metadata.jsx
@@ -832,6 +832,9 @@ export default class Metadata extends Module {
} else {
stat = el.querySelector('.ffz-stat-text');
+ if ( ! stat )
+ return destroy();
+
old_color = el.dataset.color || '';
if ( el._ffz_order !== order )
diff --git a/src/raven.js b/src/raven.js
index 5725478f..f21e407d 100644
--- a/src/raven.js
+++ b/src/raven.js
@@ -6,12 +6,16 @@
// Raven Logging
// ============================================================================
+import dayjs from 'dayjs';
+
import {DEBUG, SENTRY_ID} from 'utilities/constants';
import {has} from 'utilities/object';
import Module from 'utilities/module';
import Raven from 'raven-js';
+const STRIP_URLS = /((?:\?|&)[^?&=]*?(?:oauth|token)[^?&=]*?=)[^?&]*?(&|$)/i;
+
const AVALON_REG = /\/(?:script|static)\/((?:babel\/)?avalon)(\.js)(\?|#|$)/,
fix_url = url => url.replace(AVALON_REG, `/static/$1.${__webpack_hash__}$2$3`);
@@ -90,8 +94,8 @@ export default class RavenLogger extends Module {
});
this.settings.addUI('reports.error.example', {
- path: 'Data Management > Reporting >> Error Reports',
- component: 'example-report',
+ path: 'Data Management > Reporting >> Example Report',
+ component: 'async-text',
watch: [
'reports.error.enable',
@@ -101,7 +105,9 @@ export default class RavenLogger extends Module {
data: () => new Promise(r => {
// Why fake an error when we can *make* an error?
- this.__example_waiter = r;
+ this.__example_waiter = data => {
+ r(JSON.stringify(data, null, 4));
+ };
// Generate the error in a timeout so that the end user
// won't have a huge wall of a fake stack trace wasting
@@ -121,7 +127,7 @@ export default class RavenLogger extends Module {
this.raven = Raven;
- Raven.config(SENTRY_ID, {
+ const raven_config = {
autoBreadcrumbs: {
console: false
},
@@ -134,9 +140,6 @@ export default class RavenLogger extends Module {
'Access is denied.',
'Zugriff verweigert'
],
- whitelistUrls: [
- /cdn\.frankerfacez\.com/
- ],
sanitizeKeys: [
/Token$/
],
@@ -166,6 +169,10 @@ export default class RavenLogger extends Module {
return false;
}
+ // Don't send errors in debug mode.
+ //if ( DEBUG && !(data.tags && data.tags.example) )
+ // return false;
+
const exc = data.exception && data.exception.values[0];
// We don't want any of Sentry's junk.
@@ -207,7 +214,38 @@ export default class RavenLogger extends Module {
return true;
}
- }).install();
+ };
+
+ if ( ! DEBUG )
+ raven_config.whitelistUrls = [
+ /cdn\.frankerfacez\.com/
+ ];
+
+ Raven.config(SENTRY_ID, raven_config).install();
+ }
+
+
+ generateLog() {
+ if ( ! this.raven || ! this.raven._breadcrumbs )
+ return 'No breadcrumbs to log.';
+
+ return this.raven._breadcrumbs.map(crumb => {
+ const time = dayjs(crumb.timestamp).locale('en').format('H:mm:ss');
+ if ( crumb.type == 'http' )
+ return `[${time}] HTTP | ${crumb.category}: ${crumb.data.method} ${crumb.data.url} -> ${crumb.data.status_code}`;
+
+ let cat = 'LOG';
+ if ( crumb.category && crumb.category.includes('ui.') )
+ cat = 'UI';
+
+ return `[${time}] ${cat}${crumb.level ? `:${crumb.level}` : ''} | ${crumb.category}: ${crumb.message}${crumb.data ? `\n ${JSON.stringify(crumb.data)}` : ''}`;
+
+ }).map(x => {
+ if ( typeof x !== 'string' )
+ x = `${x}`;
+
+ return x.replace(STRIP_URLS, '$1REDACTED$2');
+ }).join('\n');
}
diff --git a/src/settings/index.js b/src/settings/index.js
index 5e152ec2..c786b4cf 100644
--- a/src/settings/index.js
+++ b/src/settings/index.js
@@ -64,6 +64,14 @@ export default class SettingsManager extends Module {
this.enable();
}
+ generateLog() {
+ const out = [];
+ for(const [key, value] of this.main_context.__cache.entries())
+ out.push(`${key}: ${JSON.stringify(value)}`);
+
+ return out.join('\n');
+ }
+
/**
* Called when the SettingsManager instance should be enabled.
*/
diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
index 898bb1ab..c295d77e 100644
--- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
+++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx
@@ -277,17 +277,19 @@ export default class EmoteMenu extends Module {
if ( ! this.props || ! has(this.props, 'channelOwnerID') || ! t.chat.context.get('chat.emote-menu.enabled') )
return old_render.call(this);
- return (
)
+ return (
+
+ )
}
this.EmoteMenu.forceUpdate();
@@ -731,6 +733,73 @@ export default class EmoteMenu extends Module {
setTimeout(doClear, 100);
};
+ this.MenuErrorWrapper = class FFZEmoteMenuErrorWrapper extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {errored: false, error: null};
+ }
+
+ static getDerivedStateFromError(error) {
+ return {
+ errored: true,
+ error
+ }
+ }
+
+ componentDidCatch(error) { // eslint-disable-line class-methods-use-this
+ t.log.capture(error);
+ t.log.error('Error rendering the FFZ Emote Menu.');
+ this.setState({
+ errored: true,
+ error
+ });
+ }
+
+ render() {
+ if ( this.state.errored ) {
+ if ( ! this.props.visible )
+ return null;
+
+ const padding = t.chat.context.get('chat.emote-menu.reduced-padding');
+
+ return (
+
+
+
+
+
+

+
+ {t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')}
+
+ {t.settings.get('reports.error.enable') ?
+ t.i18n.t('emote-menu.error-report', 'An error report has been automatically submitted.')
+ : ''
+ }
+
+ {t.i18n.t('emote-menu.disable', 'As a temporary workaround, try disabling the FFZ Emote Menu in the FFZ Control Center.') }
+
+
+
+
+
+
);
+ }
+
+ return this.props.children;
+ }
+ }
+
this.MenuComponent = class FFZEmoteMenuComponent extends React.Component {
constructor(props) {
super(props);
diff --git a/src/sites/twitch-twilight/modules/chat/index.js b/src/sites/twitch-twilight/modules/chat/index.js
index 34ec6c8e..bd9cecc9 100644
--- a/src/sites/twitch-twilight/modules/chat/index.js
+++ b/src/sites/twitch-twilight/modules/chat/index.js
@@ -105,7 +105,8 @@ const CHAT_TYPES = make_enum(
'CrateGift',
'RewardGift',
'SubMysteryGift',
- 'AnonSubMysteryGift'
+ 'AnonSubMysteryGift',
+ 'FirstCheerMessage'
);
@@ -268,7 +269,6 @@ export default class ChatHook extends Module {
});
}
-
get currentChat() {
for(const inst of this.ChatController.instances)
if ( inst && inst.chatService )
diff --git a/src/sites/twitch-twilight/modules/chat/line.js b/src/sites/twitch-twilight/modules/chat/line.js
index 4b01ba06..d57b8579 100644
--- a/src/sites/twitch-twilight/modules/chat/line.js
+++ b/src/sites/twitch-twilight/modules/chat/line.js
@@ -209,7 +209,7 @@ export default class ChatLine extends Module {
const old_render = cls.prototype.render;
cls.prototype.shouldComponentUpdate = function(props, state) {
- const show = state.alwaysShowMessage || ! props.message.deleted,
+ const show = state && state.alwaysShowMessage || ! props.message.deleted,
old_show = this._ffz_show;
// We can't just compare props.message.deleted to this.props.message.deleted
@@ -256,7 +256,7 @@ export default class ChatLine extends Module {
show = true;
show_class = msg.deleted;
} else {
- show = this.state.alwaysShowMessage || ! msg.deleted;
+ show = this.state && this.state.alwaysShowMessage || ! msg.deleted;
show_class = false;
}
diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js
index a9fdf4b2..0a22ed89 100644
--- a/src/utilities/compat/fine.js
+++ b/src/utilities/compat/fine.js
@@ -619,7 +619,7 @@ export class FineWrapper extends EventEmitter {
}
});
- this.finelog.error(`An error occured when calling forceUpdate on an instance of ${this.name}`, err);
+ this.fine.log.error(`An error occurred when calling forceUpdate on an instance of ${this.name}`, err);
}
}
diff --git a/src/utilities/dialog.js b/src/utilities/dialog.js
index dc20e762..4071717b 100644
--- a/src/utilities/dialog.js
+++ b/src/utilities/dialog.js
@@ -116,7 +116,7 @@ export default class Dialog extends EventEmitter {
visible = this._visible = ! this._visible,
container = this.getContainer();
- if ( maximized )
+ if ( maximized && container )
container.classList.toggle('ffz-has-dialog', visible);
if ( ! visible ) {
@@ -129,6 +129,9 @@ export default class Dialog extends EventEmitter {
return;
}
+ if ( ! container )
+ return;
+
if ( this.factory ) {
const el = this.factory();
if ( el instanceof Promise ) {
@@ -163,13 +166,15 @@ export default class Dialog extends EventEmitter {
if ( container === old_container )
return;
- if ( maximized )
- container.classList.add('ffz-has-dialog');
- else
+ if ( maximized ) {
+ if ( container )
+ container.classList.add('ffz-has-dialog');
+ } else if ( old_container )
old_container.classList.remove('ffz-has-dialog');
this._element.remove();
- container.appendChild(this._element);
+ if ( container )
+ container.appendChild(this._element);
this.emit('resize');
}
diff --git a/src/utilities/logging.js b/src/utilities/logging.js
index 95fbc372..72d82ea2 100644
--- a/src/utilities/logging.js
+++ b/src/utilities/logging.js
@@ -10,9 +10,14 @@ const RAVEN_LEVELS = {
export default class Logger {
constructor(parent, name, level, raven) {
+ this.root = parent ? parent.root : this;
this.parent = parent;
this.name = name;
+ if ( this.root == this )
+ this.captured_init = [];
+
+ this.init = false;
this.enabled = true;
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
this.raven = raven || (parent && parent.raven);
@@ -68,6 +73,14 @@ export default class Logger {
const message = Array.prototype.slice.call(args);
+ if ( this.root.init )
+ this.root.captured_init.push({
+ time: Date.now(),
+ category: this.name,
+ message: message.join(' '),
+ level: RAVEN_LEVELS[level] || level
+ });
+
this.crumb({
message: message.join(' '),
category: this.name,