mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-05 18:48:31 +00:00
4.0.0-rc13.11
* Added: Debug Log generation and easy uploading for issue reports. * Fixed: Bug with chat lines not having a state causing rendering to crash. * Fixed: Add an error boundary around the custom FFZ Emote Menu to catch errors, displaying feedback and sending error reports rather than silently failing. * Fixed: Typo in Fine when logging errors. * Changed: Update chat types with the latest values from Twitch's source. * Changed: Update the GitHub issue template with instructions on uploading logs from v4.
This commit is contained in:
parent
cb2f2b19ee
commit
d6504757b3
18 changed files with 413 additions and 99 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Do you use BetterTTV or other Twitch extensions**:
|
**Do you use BetterTTV or other Twitch extensions**:
|
||||||
|
|
||||||
**FFZ Logs (via FFZ Menu > About > Logs; if Applicable)**:
|
**FFZ Logs (via FFZ Control Center > Home > Feedback >> Log; if Applicable)**:
|
||||||
|
|
||||||
|
|
||||||
**Bug / Idea**:
|
**Bug / Idea**:
|
||||||
|
|
|
@ -123,6 +123,25 @@ export default class ExperimentManager extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
generateLog() {
|
||||||
|
const out = [
|
||||||
|
`Unique ID: ${this.unique_id}`,
|
||||||
|
''
|
||||||
|
];
|
||||||
|
|
||||||
|
for(const [key, value] of Object.entries(this.experiments)) {
|
||||||
|
out.push(`FFZ | ${value.name}: ${this.getAssignment(key)}${this.hasOverride(key) ? ' (Overriden)' : ''}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const [key, value] of Object.entries(this.getTwitchExperiments())) {
|
||||||
|
if ( this.usingTwitchExperiment(key) )
|
||||||
|
out.push(`TWITCH | ${value.name}: ${this.getTwitchAssignment(key)}${this.hasTwitchOverride(key) ? ' (Overriden)' : ''}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Twitch Experiments
|
// Twitch Experiments
|
||||||
|
|
||||||
getTwitchExperiments() {
|
getTwitchExperiments() {
|
||||||
|
|
56
src/main.js
56
src/main.js
|
@ -1,5 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import RavenLogger from './raven';
|
import RavenLogger from './raven';
|
||||||
|
|
||||||
import Logger from 'utilities/logging';
|
import Logger from 'utilities/logging';
|
||||||
|
@ -13,6 +14,8 @@ import {TranslationManager} from './i18n';
|
||||||
import SocketClient from './socket';
|
import SocketClient from './socket';
|
||||||
import Site from 'site';
|
import Site from 'site';
|
||||||
import Vue from 'utilities/vue';
|
import Vue from 'utilities/vue';
|
||||||
|
import { timeout } from './utilities/object';
|
||||||
|
import { strict } from 'assert';
|
||||||
|
|
||||||
class FrankerFaceZ extends Module {
|
class FrankerFaceZ extends Module {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -30,10 +33,11 @@ class FrankerFaceZ extends Module {
|
||||||
// Error Reporting and Logging
|
// Error Reporting and Logging
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
if ( ! DEBUG )
|
this.inject('raven', RavenLogger);
|
||||||
this.inject('raven', RavenLogger);
|
|
||||||
|
|
||||||
this.log = new Logger(null, null, null, this.raven);
|
this.log = new Logger(null, null, null, this.raven);
|
||||||
|
this.log.init = true;
|
||||||
|
|
||||||
this.core_log = this.log.get('core');
|
this.core_log = this.log.get('core');
|
||||||
|
|
||||||
this.log.info(`FrankerFaceZ v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`);
|
this.log.info(`FrankerFaceZ v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`);
|
||||||
|
@ -61,9 +65,11 @@ class FrankerFaceZ extends Module {
|
||||||
this.enable().then(() => this.enableInitialModules()).then(() => {
|
this.enable().then(() => this.enableInitialModules()).then(() => {
|
||||||
const duration = performance.now() - start_time;
|
const duration = performance.now() - start_time;
|
||||||
this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
|
this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
|
||||||
|
this.log.init = false;
|
||||||
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.core_log.error('An error occurred during initialization.', err);
|
this.core_log.error('An error occurred during initialization.', err);
|
||||||
|
this.log.init = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +78,50 @@ class FrankerFaceZ extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Generate Log
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
async generateLog() {
|
||||||
|
const promises = [];
|
||||||
|
for(const key in this.__modules) {
|
||||||
|
const module = this.__modules[key];
|
||||||
|
if ( module instanceof Module && module.generateLog && module != this )
|
||||||
|
promises.push((async () => {
|
||||||
|
try {
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
await timeout(Promise.resolve(module.generateLog()), 5000)
|
||||||
|
];
|
||||||
|
} catch(err) {
|
||||||
|
return [
|
||||||
|
key,
|
||||||
|
`Error: ${err}`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})());
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = await Promise.all(promises);
|
||||||
|
|
||||||
|
if ( this.log.captured_init && this.log.captured_init.length > 0 ) {
|
||||||
|
const logs = [];
|
||||||
|
for(const msg of this.log.captured_init) {
|
||||||
|
const time = dayjs(msg.time).locale('en').format('H:mm:ss');
|
||||||
|
logs.push(`[${time}] ${msg.level} | ${msg.category || 'core'}: ${msg.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.unshift(['initialization', logs.join('\n')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.map(x => {
|
||||||
|
return `${x[0]}
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
|
||||||
|
}).join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Modules
|
// Modules
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
@ -100,7 +150,7 @@ class FrankerFaceZ extends Module {
|
||||||
FrankerFaceZ.Logger = Logger;
|
FrankerFaceZ.Logger = Logger;
|
||||||
|
|
||||||
const VER = FrankerFaceZ.version_info = {
|
const VER = FrankerFaceZ.version_info = {
|
||||||
major: 4, minor: 0, revision: 0, extra: '-rc13.10',
|
major: 4, minor: 0, revision: 0, extra: '-rc13.11',
|
||||||
commit: __git_commit__,
|
commit: __git_commit__,
|
||||||
build: __webpack_hash__,
|
build: __webpack_hash__,
|
||||||
toString: () =>
|
toString: () =>
|
||||||
|
|
|
@ -267,6 +267,9 @@ export default class Badges extends Module {
|
||||||
data = JSON.parse(target.dataset.badgeData),
|
data = JSON.parse(target.dataset.badgeData),
|
||||||
out = [];
|
out = [];
|
||||||
|
|
||||||
|
if ( data == null )
|
||||||
|
return out;
|
||||||
|
|
||||||
for(const d of data) {
|
for(const d of data) {
|
||||||
const p = d.provider;
|
const p = d.provider;
|
||||||
if ( p === 'twitch' ) {
|
if ( p === 'twitch' ) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Chat
|
// Chat
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
@ -559,6 +560,15 @@ export default class Chat extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
generateLog() {
|
||||||
|
const out = ['chat settings', '-------------------------------------------------------------------------------'];
|
||||||
|
for(const [key, value] of this.context.__cache.entries())
|
||||||
|
out.push(`${key}: ${JSON.stringify(value)}`);
|
||||||
|
|
||||||
|
return out.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
for(const key in TOKENIZERS)
|
for(const key in TOKENIZERS)
|
||||||
if ( has(TOKENIZERS, key) )
|
if ( has(TOKENIZERS, key) )
|
||||||
|
|
145
src/modules/main_menu/components/async-text.vue
Normal file
145
src/modules/main_menu/components/async-text.vue
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="ffz--example-report tw-relative ">
|
||||||
|
<div v-if="canUpload" class="tw-absolute ffz--report-upload">
|
||||||
|
<div v-if="uploading">
|
||||||
|
<button
|
||||||
|
class="tw-button tw-button--disabled"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-upload-cloud" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('async-text.uploading', 'Uploading...') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="url">
|
||||||
|
<input
|
||||||
|
ref="url_box"
|
||||||
|
:value="url"
|
||||||
|
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
@focusin="selectURL"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<button
|
||||||
|
class="tw-button"
|
||||||
|
@click="upload"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-upload-cloud" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('async-text.upload', 'Upload') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-c-background-alt-2 tw-font-size-5 tw-pd-y-05 tw-pd-x-1 tw-border-radius-large">
|
||||||
|
<div v-if="loading" class="tw-align-center">
|
||||||
|
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
|
||||||
|
</div>
|
||||||
|
<code v-else>{{ text }}</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['item', 'context'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
uploading: false,
|
||||||
|
url: null,
|
||||||
|
|
||||||
|
loading: false,
|
||||||
|
text: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
canUpload() {
|
||||||
|
return ! this.loading && this.text;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.refresh();
|
||||||
|
|
||||||
|
const ctx = this.context.context;
|
||||||
|
for(const key of this.item.watch)
|
||||||
|
ctx.on(`changed:${key}`, this.refresh, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
const ctx = this.context.context;
|
||||||
|
for(const key of this.item.watch)
|
||||||
|
ctx.off(`changed:${key}`, this.refresh, this);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
selectURL() {
|
||||||
|
if ( this.$refs && this.$refs.url_box )
|
||||||
|
this.$refs.url_box.select();
|
||||||
|
},
|
||||||
|
|
||||||
|
async upload() {
|
||||||
|
if ( this.uploading || this.url || this.text == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.uploading = true;
|
||||||
|
const response = await fetch('https://putco.de', {
|
||||||
|
method: 'PUT',
|
||||||
|
body: this.text
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( ! response.ok ) {
|
||||||
|
this.uploading = false;
|
||||||
|
this.url = 'An error occured.';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.url = await response.text();
|
||||||
|
if ( this.url.startsWith('http://') )
|
||||||
|
this.url = `https://${this.url.slice(7)}`;
|
||||||
|
|
||||||
|
this.uploading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.uploading = false;
|
||||||
|
this.url = null;
|
||||||
|
this.loading = true;
|
||||||
|
this.text = await this.item.data();
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ffz--example-report {
|
||||||
|
div {
|
||||||
|
max-height: 30rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz--report-upload {
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
right: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,58 +0,0 @@
|
||||||
<template lang="html">
|
|
||||||
<div class="ffz--example-report">
|
|
||||||
<h4 class="tw-mg-b-05">{{ t('reports.example', 'Example Report') }}</h4>
|
|
||||||
<div class="tw-c-background-alt-2 tw-font-size-5 tw-pd-y-05 tw-pd-x-1 tw-border-radius-large">
|
|
||||||
<code>{{ JSON.stringify(example, null, 4) }}</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['item', 'context'],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
example: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.refresh();
|
|
||||||
|
|
||||||
const ctx = this.context.context;
|
|
||||||
for(const key of this.item.watch)
|
|
||||||
ctx.on(`changed:${key}`, this.refresh, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed() {
|
|
||||||
const ctx = this.context.context;
|
|
||||||
for(const key of this.item.watch)
|
|
||||||
ctx.off(`changed:${key}`, this.refresh, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
refresh() {
|
|
||||||
this.example = 'Loading...';
|
|
||||||
this.item.data().then(data => this.example = data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.ffz--example-report {
|
|
||||||
div {
|
|
||||||
max-height: 30rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: monospace;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,15 +1,9 @@
|
||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="ffz--home tw-border-t tw-pd-t-1">
|
<div class="ffz--home tw-border-t tw-pd-y-1">
|
||||||
<h2>Feedback</h2>
|
<h2>Feedback</h2>
|
||||||
|
|
||||||
<div class="tw-mg-y-1 tw-c-background-accent tw-c-text-overlay tw-pd-1">
|
|
||||||
<h3 class="ffz-i-attention">
|
|
||||||
Please keep in mind that FrankerFaceZ v4 is under heavy development.
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Okay, still here? Great! You can provide feedback and bug reports by
|
You can provide feedback and bug reports by
|
||||||
<a href="https://github.com/FrankerFaceZ/FrankerFaceZ/issues" target="_blank" rel="noopener">
|
<a href="https://github.com/FrankerFaceZ/FrankerFaceZ/issues" target="_blank" rel="noopener">
|
||||||
opening an issue at our GitHub repository</a>.
|
opening an issue at our GitHub repository</a>.
|
||||||
|
|
||||||
|
@ -21,6 +15,11 @@
|
||||||
When creating a GitHub issue, please check that someone else hasn't
|
When creating a GitHub issue, please check that someone else hasn't
|
||||||
already created one for what you'd like to discuss or report.
|
already created one for what you'd like to discuss or report.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When creating an issue, please also upload the following logs and
|
||||||
|
include a link in your report.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,16 @@ export default class MainMenu extends Module {
|
||||||
component: 'feedback-page'
|
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', {
|
this.settings.addUI('changelog', {
|
||||||
path: 'Home > Changelog',
|
path: 'Home > Changelog',
|
||||||
component: 'changelog'
|
component: 'changelog'
|
||||||
|
|
|
@ -832,6 +832,9 @@ export default class Metadata extends Module {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
stat = el.querySelector('.ffz-stat-text');
|
stat = el.querySelector('.ffz-stat-text');
|
||||||
|
if ( ! stat )
|
||||||
|
return destroy();
|
||||||
|
|
||||||
old_color = el.dataset.color || '';
|
old_color = el.dataset.color || '';
|
||||||
|
|
||||||
if ( el._ffz_order !== order )
|
if ( el._ffz_order !== order )
|
||||||
|
|
54
src/raven.js
54
src/raven.js
|
@ -6,12 +6,16 @@
|
||||||
// Raven Logging
|
// Raven Logging
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {DEBUG, SENTRY_ID} from 'utilities/constants';
|
import {DEBUG, SENTRY_ID} from 'utilities/constants';
|
||||||
import {has} from 'utilities/object';
|
import {has} from 'utilities/object';
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
|
|
||||||
import Raven from 'raven-js';
|
import Raven from 'raven-js';
|
||||||
|
|
||||||
|
const STRIP_URLS = /((?:\?|&)[^?&=]*?(?:oauth|token)[^?&=]*?=)[^?&]*?(&|$)/i;
|
||||||
|
|
||||||
const AVALON_REG = /\/(?:script|static)\/((?:babel\/)?avalon)(\.js)(\?|#|$)/,
|
const AVALON_REG = /\/(?:script|static)\/((?:babel\/)?avalon)(\.js)(\?|#|$)/,
|
||||||
fix_url = url => url.replace(AVALON_REG, `/static/$1.${__webpack_hash__}$2$3`);
|
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', {
|
this.settings.addUI('reports.error.example', {
|
||||||
path: 'Data Management > Reporting >> Error Reports',
|
path: 'Data Management > Reporting >> Example Report',
|
||||||
component: 'example-report',
|
component: 'async-text',
|
||||||
|
|
||||||
watch: [
|
watch: [
|
||||||
'reports.error.enable',
|
'reports.error.enable',
|
||||||
|
@ -101,7 +105,9 @@ export default class RavenLogger extends Module {
|
||||||
|
|
||||||
data: () => new Promise(r => {
|
data: () => new Promise(r => {
|
||||||
// Why fake an error when we can *make* an error?
|
// 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
|
// Generate the error in a timeout so that the end user
|
||||||
// won't have a huge wall of a fake stack trace wasting
|
// 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;
|
this.raven = Raven;
|
||||||
|
|
||||||
Raven.config(SENTRY_ID, {
|
const raven_config = {
|
||||||
autoBreadcrumbs: {
|
autoBreadcrumbs: {
|
||||||
console: false
|
console: false
|
||||||
},
|
},
|
||||||
|
@ -134,9 +140,6 @@ export default class RavenLogger extends Module {
|
||||||
'Access is denied.',
|
'Access is denied.',
|
||||||
'Zugriff verweigert'
|
'Zugriff verweigert'
|
||||||
],
|
],
|
||||||
whitelistUrls: [
|
|
||||||
/cdn\.frankerfacez\.com/
|
|
||||||
],
|
|
||||||
sanitizeKeys: [
|
sanitizeKeys: [
|
||||||
/Token$/
|
/Token$/
|
||||||
],
|
],
|
||||||
|
@ -166,6 +169,10 @@ export default class RavenLogger extends Module {
|
||||||
return false;
|
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];
|
const exc = data.exception && data.exception.values[0];
|
||||||
|
|
||||||
// We don't want any of Sentry's junk.
|
// We don't want any of Sentry's junk.
|
||||||
|
@ -207,7 +214,38 @@ export default class RavenLogger extends Module {
|
||||||
|
|
||||||
return true;
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,14 @@ export default class SettingsManager extends Module {
|
||||||
this.enable();
|
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.
|
* Called when the SettingsManager instance should be enabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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') )
|
if ( ! this.props || ! has(this.props, 'channelOwnerID') || ! t.chat.context.get('chat.emote-menu.enabled') )
|
||||||
return old_render.call(this);
|
return old_render.call(this);
|
||||||
|
|
||||||
return (<t.MenuComponent
|
return (<t.MenuErrorWrapper visible={this.props.visible}>
|
||||||
visible={this.props.visible}
|
<t.MenuComponent
|
||||||
toggleVisibility={this.props.toggleVisibility}
|
visible={this.props.visible}
|
||||||
onClickEmote={this.props.onClickEmote}
|
toggleVisibility={this.props.toggleVisibility}
|
||||||
channel_data={this.props.channelData}
|
onClickEmote={this.props.onClickEmote}
|
||||||
emote_data={this.props.emoteSetsData}
|
channel_data={this.props.channelData}
|
||||||
user_id={this.props.currentUserID}
|
emote_data={this.props.emoteSetsData}
|
||||||
channel_id={this.props.channelOwnerID}
|
user_id={this.props.currentUserID}
|
||||||
loading={this.state.gqlLoading}
|
channel_id={this.props.channelOwnerID}
|
||||||
error={this.state.gqlError}
|
loading={this.state.gqlLoading}
|
||||||
/>)
|
error={this.state.gqlError}
|
||||||
|
/>
|
||||||
|
</t.MenuErrorWrapper>)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.EmoteMenu.forceUpdate();
|
this.EmoteMenu.forceUpdate();
|
||||||
|
@ -731,6 +733,73 @@ export default class EmoteMenu extends Module {
|
||||||
setTimeout(doClear, 100);
|
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 (<div
|
||||||
|
class={`tw-balloon tw-balloon--md tw-balloon--up tw-balloon--right tw-block tw-absolute ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||||
|
data-a-target="emote-picker"
|
||||||
|
>
|
||||||
|
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base">
|
||||||
|
<div
|
||||||
|
class="emote-picker__tab-content scrollable-area"
|
||||||
|
data-test-selector="scrollable-area-wrapper"
|
||||||
|
data-simplebar
|
||||||
|
>
|
||||||
|
<div class="tw-align-center tw-pd-1">
|
||||||
|
<div class="tw-mg-b-1">
|
||||||
|
<div class="tw-mg-2">
|
||||||
|
<img
|
||||||
|
src="//cdn.frankerfacez.com/emoticon/26608/2"
|
||||||
|
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')}
|
||||||
|
<br />
|
||||||
|
{t.settings.get('reports.error.enable') ?
|
||||||
|
t.i18n.t('emote-menu.error-report', 'An error report has been automatically submitted.')
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
<div class="tw-mg-t-05 tw-border-t-1 tw-pd-t-05">
|
||||||
|
{t.i18n.t('emote-menu.disable', 'As a temporary workaround, try disabling the FFZ Emote Menu in the FFZ Control Center.') }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.MenuComponent = class FFZEmoteMenuComponent extends React.Component {
|
this.MenuComponent = class FFZEmoteMenuComponent extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
|
@ -105,7 +105,8 @@ const CHAT_TYPES = make_enum(
|
||||||
'CrateGift',
|
'CrateGift',
|
||||||
'RewardGift',
|
'RewardGift',
|
||||||
'SubMysteryGift',
|
'SubMysteryGift',
|
||||||
'AnonSubMysteryGift'
|
'AnonSubMysteryGift',
|
||||||
|
'FirstCheerMessage'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -268,7 +269,6 @@ export default class ChatHook extends Module {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get currentChat() {
|
get currentChat() {
|
||||||
for(const inst of this.ChatController.instances)
|
for(const inst of this.ChatController.instances)
|
||||||
if ( inst && inst.chatService )
|
if ( inst && inst.chatService )
|
||||||
|
|
|
@ -209,7 +209,7 @@ export default class ChatLine extends Module {
|
||||||
const old_render = cls.prototype.render;
|
const old_render = cls.prototype.render;
|
||||||
|
|
||||||
cls.prototype.shouldComponentUpdate = function(props, state) {
|
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;
|
old_show = this._ffz_show;
|
||||||
|
|
||||||
// We can't just compare props.message.deleted to this.props.message.deleted
|
// 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 = true;
|
||||||
show_class = msg.deleted;
|
show_class = msg.deleted;
|
||||||
} else {
|
} else {
|
||||||
show = this.state.alwaysShowMessage || ! msg.deleted;
|
show = this.state && this.state.alwaysShowMessage || ! msg.deleted;
|
||||||
show_class = false;
|
show_class = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ export default class Dialog extends EventEmitter {
|
||||||
visible = this._visible = ! this._visible,
|
visible = this._visible = ! this._visible,
|
||||||
container = this.getContainer();
|
container = this.getContainer();
|
||||||
|
|
||||||
if ( maximized )
|
if ( maximized && container )
|
||||||
container.classList.toggle('ffz-has-dialog', visible);
|
container.classList.toggle('ffz-has-dialog', visible);
|
||||||
|
|
||||||
if ( ! visible ) {
|
if ( ! visible ) {
|
||||||
|
@ -129,6 +129,9 @@ export default class Dialog extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! container )
|
||||||
|
return;
|
||||||
|
|
||||||
if ( this.factory ) {
|
if ( this.factory ) {
|
||||||
const el = this.factory();
|
const el = this.factory();
|
||||||
if ( el instanceof Promise ) {
|
if ( el instanceof Promise ) {
|
||||||
|
@ -163,13 +166,15 @@ export default class Dialog extends EventEmitter {
|
||||||
if ( container === old_container )
|
if ( container === old_container )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ( maximized )
|
if ( maximized ) {
|
||||||
container.classList.add('ffz-has-dialog');
|
if ( container )
|
||||||
else
|
container.classList.add('ffz-has-dialog');
|
||||||
|
} else if ( old_container )
|
||||||
old_container.classList.remove('ffz-has-dialog');
|
old_container.classList.remove('ffz-has-dialog');
|
||||||
|
|
||||||
this._element.remove();
|
this._element.remove();
|
||||||
container.appendChild(this._element);
|
if ( container )
|
||||||
|
container.appendChild(this._element);
|
||||||
|
|
||||||
this.emit('resize');
|
this.emit('resize');
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,14 @@ const RAVEN_LEVELS = {
|
||||||
|
|
||||||
export default class Logger {
|
export default class Logger {
|
||||||
constructor(parent, name, level, raven) {
|
constructor(parent, name, level, raven) {
|
||||||
|
this.root = parent ? parent.root : this;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
|
if ( this.root == this )
|
||||||
|
this.captured_init = [];
|
||||||
|
|
||||||
|
this.init = false;
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
|
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
|
||||||
this.raven = raven || (parent && parent.raven);
|
this.raven = raven || (parent && parent.raven);
|
||||||
|
@ -68,6 +73,14 @@ export default class Logger {
|
||||||
|
|
||||||
const message = Array.prototype.slice.call(args);
|
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({
|
this.crumb({
|
||||||
message: message.join(' '),
|
message: message.join(' '),
|
||||||
category: this.name,
|
category: this.name,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue