mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-10-13 22:41:57 +00:00
The Report Your Errors Update
* Add automatic error reporting with Sentry.io * Filter a bunch of bad errors from showing up on Sentry * Add module.hasModule method. * Fix deep_copy * Fix disallow mouse interaction with extensions * Add some new icons to the icon font for mod cards * Allow Ctrl-Shift-Clicking emotes. * Rarity sorting for experiments and unset display for unused experiments.
This commit is contained in:
parent
e3a7e3b64d
commit
d7a07a5612
32 changed files with 575 additions and 83 deletions
|
@ -8,6 +8,28 @@
|
|||
import Module from 'utilities/module';
|
||||
import {has, get} from 'utilities/object';
|
||||
|
||||
|
||||
const BAD_ERRORS = [
|
||||
'timeout',
|
||||
'unable to load',
|
||||
'error internal',
|
||||
'Internal Server Error'
|
||||
];
|
||||
|
||||
function skip_error(err) {
|
||||
for(const m of BAD_ERRORS)
|
||||
if ( err.message.includes(m) )
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export class GQLError extends Error {
|
||||
constructor(err) {
|
||||
super(`${err.message}; Location: ${err.locations}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class Apollo extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
@ -17,6 +39,7 @@ export default class Apollo extends Module {
|
|||
|
||||
this.inject('..web_munch');
|
||||
this.inject('..fine');
|
||||
//this.inject('core');
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
|
@ -62,6 +85,10 @@ export default class Apollo extends Module {
|
|||
if ( ! this.enabled )
|
||||
return forward(operation);
|
||||
|
||||
let vars = operation.variables;
|
||||
if ( ! Object.keys(vars).length )
|
||||
vars = undefined;
|
||||
|
||||
try {
|
||||
// ONLY do this if we've hooked query init, thus letting us ignore certain issues
|
||||
// that would cause Twitch to show lovely "Error loading data" messages everywhere.
|
||||
|
@ -69,6 +96,14 @@ export default class Apollo extends Module {
|
|||
this.apolloPreFlight(operation);
|
||||
|
||||
} catch(err) {
|
||||
this.log.capture(err, {
|
||||
tags: {
|
||||
operation: operation.operationName
|
||||
},
|
||||
extra: {
|
||||
variables: vars
|
||||
}
|
||||
});
|
||||
this.log.error('Error running Pre-Flight', err, operation);
|
||||
return forward(operation);
|
||||
}
|
||||
|
@ -80,9 +115,45 @@ export default class Apollo extends Module {
|
|||
try {
|
||||
out.subscribe({
|
||||
next: result => {
|
||||
if ( result.errors ) {
|
||||
const name = operation.operationName;
|
||||
if ( name.includes('FFZ') || has(this.modifiers, name) || has(this.post_modifiers, name) ) {
|
||||
for(const err of result.errors) {
|
||||
if ( skip_error(err) )
|
||||
continue;
|
||||
|
||||
this.log.capture(new GQLError(err), {
|
||||
tags: {
|
||||
operation: operation.operationName
|
||||
},
|
||||
extra: {
|
||||
variables: vars
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.log.crumb({
|
||||
level: 'info',
|
||||
category: 'gql',
|
||||
message: `${operation.operationName} [${result.extensions && result.extensions.durationMilliseconds || '??'}ms]`,
|
||||
data: {
|
||||
variables: vars,
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
this.apolloPostFlight(result);
|
||||
} catch(err) {
|
||||
this.log.capture(err, {
|
||||
tags: {
|
||||
operation: operation.operationName
|
||||
},
|
||||
extra: {
|
||||
variables: vars
|
||||
}
|
||||
});
|
||||
this.log.error('Error running Post-Flight', err, result);
|
||||
}
|
||||
|
||||
|
@ -97,6 +168,14 @@ export default class Apollo extends Module {
|
|||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.capture(err, {
|
||||
tags: {
|
||||
operation: operation.operationName
|
||||
},
|
||||
extra: {
|
||||
variables: vars
|
||||
}
|
||||
});
|
||||
this.log.error('Link Error', err);
|
||||
observer.error(err);
|
||||
}
|
||||
|
|
|
@ -61,14 +61,14 @@ export default class FineRouter extends Module {
|
|||
this.current = route;
|
||||
this.current_name = route.name;
|
||||
this.match = match;
|
||||
this.emitSafe(':route', route, match);
|
||||
this.emitSafe(`:route:${route.name}`, ...match);
|
||||
this.emit(':route', route, match);
|
||||
this.emit(`:route:${route.name}`, ...match);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.current = this.current_name = this.match = null;
|
||||
this.emitSafe(':route', null, null);
|
||||
this.emit(':route', null, null);
|
||||
}
|
||||
|
||||
route(name, path) {
|
||||
|
|
|
@ -573,7 +573,13 @@ export class FineWrapper extends EventEmitter {
|
|||
try {
|
||||
inst.forceUpdate();
|
||||
} catch(err) {
|
||||
this.fine.log.error(`An error occured when calling forceUpdate on an instance of ${this.name}`, err);
|
||||
this.fine.log.capture(err, {
|
||||
tags: {
|
||||
fine_wrapper: this.name
|
||||
}
|
||||
});
|
||||
|
||||
this.finelog.error(`An error occured when calling forceUpdate on an instance of ${this.name}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com
|
|||
export const CLIENT_ID = 'a3bc9znoz6vi8ozsoca0inlcr4fcvkl';
|
||||
export const API_SERVER = '//api.frankerfacez.com';
|
||||
|
||||
export const SENTRY_ID = 'https://18f42c65339d4164b3fdebfc8c8bc99b@sentry.io/1186960';
|
||||
|
||||
export const TWITCH_EMOTE_BASE = '//static-cdn.jtvnw.net/emoticons/v1/';
|
||||
|
||||
export const KNOWN_CODES = {
|
||||
|
|
|
@ -178,7 +178,7 @@ export class ClickOutside {
|
|||
}
|
||||
|
||||
handleClick(e) {
|
||||
if ( ! this.el.contains(e.target) )
|
||||
if ( this.el && ! this.el.contains(e.target) )
|
||||
this.cb(e);
|
||||
}
|
||||
}
|
|
@ -119,7 +119,7 @@ export class EventEmitter {
|
|||
return list ? Array.from(list) : [];
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
emitUnsafe(event, ...args) {
|
||||
const list = this.__listeners[event];
|
||||
if ( ! list )
|
||||
return;
|
||||
|
@ -160,28 +160,25 @@ export class EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
emitSafe(event, ...args) {
|
||||
try {
|
||||
return [this.emit(event, ...args), undefined];
|
||||
|
||||
} catch(err) {
|
||||
return [null, err];
|
||||
}
|
||||
}
|
||||
|
||||
emitAsync(event, ...args) {
|
||||
emit(event, ...args) {
|
||||
const list = this.__listeners[event];
|
||||
if ( ! list )
|
||||
return Promise.resolve([]);
|
||||
return;
|
||||
|
||||
// Track removals separately to make iteration over the event list
|
||||
// much, much simpler.
|
||||
const removed = new Set,
|
||||
promises = [];
|
||||
const removed = new Set;
|
||||
|
||||
for(const item of list) {
|
||||
const [fn, ctx, ttl] = item;
|
||||
const ret = fn.apply(ctx, args);
|
||||
let ret;
|
||||
try {
|
||||
ret = fn.apply(ctx, args);
|
||||
} catch(err) {
|
||||
if ( this.log )
|
||||
this.log.capture(err, {tags: {event}, extra:{args}});
|
||||
}
|
||||
|
||||
if ( ret === Detach )
|
||||
removed.add(item);
|
||||
else if ( ttl !== false ) {
|
||||
|
@ -190,9 +187,6 @@ export class EventEmitter {
|
|||
else
|
||||
item[2] = ttl - 1;
|
||||
}
|
||||
|
||||
if ( ret !== Detach )
|
||||
promises.push(ret);
|
||||
}
|
||||
|
||||
if ( removed.size ) {
|
||||
|
@ -211,8 +205,72 @@ export class EventEmitter {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
async emitAsync(event, ...args) {
|
||||
const list = this.__listeners[event];
|
||||
if ( ! list )
|
||||
return [];
|
||||
|
||||
// Track removals separately to make iteration over the event list
|
||||
// much, much simpler.
|
||||
const removed = new Set,
|
||||
promises = [];
|
||||
|
||||
for(const item of list) {
|
||||
const [fn, ctx] = item;
|
||||
let ret;
|
||||
try {
|
||||
ret = fn.apply(ctx, args);
|
||||
} catch(err) {
|
||||
if ( this.log )
|
||||
this.log.capture(err, {tags: {event}, extra: {args}});
|
||||
}
|
||||
|
||||
if ( !(ret instanceof Promise) )
|
||||
ret = Promise.resolve(ret);
|
||||
|
||||
promises.push(ret.then(r => {
|
||||
const new_ttl = item[2];
|
||||
if ( r === Detach )
|
||||
removed.add(item);
|
||||
else if ( new_ttl !== false ) {
|
||||
if ( new_ttl <= 1 )
|
||||
removed.add(item);
|
||||
else
|
||||
item[2] = new_ttl - 1;
|
||||
}
|
||||
|
||||
if ( ret !== Detach )
|
||||
return ret;
|
||||
}).catch(err => {
|
||||
if ( this.log )
|
||||
this.log.capture(err, {event, args});
|
||||
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
const out = await Promise.all(promises);
|
||||
|
||||
if ( removed.size ) {
|
||||
// Re-grab the list to make sure it wasn't removed mid-iteration.
|
||||
const new_list = this.__listeners[event];
|
||||
if ( new_list ) {
|
||||
for(const item of removed) {
|
||||
const idx = new_list.indexOf(item);
|
||||
if ( idx !== -1 )
|
||||
new_list.splice(idx, 1);
|
||||
}
|
||||
|
||||
if ( ! list.length ) {
|
||||
this.__listeners[event] = null;
|
||||
this.__dead_events++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
const RAVEN_LEVELS = {
|
||||
1: 'debug',
|
||||
2: 'info',
|
||||
4: 'warn',
|
||||
8: 'error'
|
||||
};
|
||||
|
||||
|
||||
export default class Logger {
|
||||
constructor(parent, name, level) {
|
||||
constructor(parent, name, level, raven) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
this.enabled = true;
|
||||
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
|
||||
this.raven = raven || (parent && parent.raven);
|
||||
|
||||
this.children = {};
|
||||
}
|
||||
|
@ -34,6 +43,24 @@ export default class Logger {
|
|||
return this.invoke(Logger.ERROR, args);
|
||||
}
|
||||
|
||||
crumb(...args) {
|
||||
if ( this.raven )
|
||||
return this.raven.captureBreadcrumb(...args);
|
||||
}
|
||||
|
||||
capture(exc, opts, ...args) {
|
||||
if ( this.raven ) {
|
||||
opts = opts || {};
|
||||
if ( ! opts.logger )
|
||||
opts.logger = this.name;
|
||||
|
||||
this.raven.captureException(exc, opts);
|
||||
}
|
||||
|
||||
if ( args.length )
|
||||
return this.error(...args);
|
||||
}
|
||||
|
||||
/* eslint no-console: "off" */
|
||||
invoke(level, args) {
|
||||
if ( ! this.enabled || level < this.level )
|
||||
|
@ -41,6 +68,12 @@ export default class Logger {
|
|||
|
||||
const message = Array.prototype.slice.call(args);
|
||||
|
||||
this.crumb({
|
||||
message: message.join(' '),
|
||||
category: this.name,
|
||||
level: RAVEN_LEVELS[level] || level
|
||||
});
|
||||
|
||||
if ( this.name )
|
||||
message.unshift(`%cFFZ [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||
else
|
||||
|
|
|
@ -380,6 +380,12 @@ export class Module extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
hasModule(name) {
|
||||
const module = this.__modules[this.abs_path(name)];
|
||||
return module instanceof Module;
|
||||
}
|
||||
|
||||
|
||||
__get_requires() {
|
||||
if ( has(this, 'requires') )
|
||||
return this.requires;
|
||||
|
@ -430,6 +436,8 @@ export class Module extends EventEmitter {
|
|||
else
|
||||
this.__modules[this.abs_path(full_name)] = [[], [], [[this.__path, name]]]
|
||||
|
||||
requires.push(this.abs_path(full_name));
|
||||
|
||||
return this[name] = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -198,14 +198,14 @@ export function deep_copy(object, seen) {
|
|||
seen.add(object);
|
||||
|
||||
if ( Array.isArray(object) )
|
||||
return object.map(x => deep_copy(x, seen));
|
||||
return object.map(x => deep_copy(x, new Set(seen)));
|
||||
|
||||
const out = {};
|
||||
for(const key in object)
|
||||
if ( HOP.call(object, key) ) {
|
||||
const val = object[key];
|
||||
if ( typeof val === 'object' )
|
||||
out[key] = deep_copy(val, seen);
|
||||
out[key] = deep_copy(val, new Set(seen));
|
||||
else
|
||||
out[key] = val;
|
||||
}
|
||||
|
|
|
@ -18,10 +18,14 @@ export class Vue extends Module {
|
|||
|
||||
async onLoad() {
|
||||
const Vue = window.ffzVue = this.Vue = (await import(/* webpackChunkName: "vue" */ 'vue')).default,
|
||||
RavenVue = await import(/* webpackChunkName: "vue" */ 'raven-js/plugins/vue'),
|
||||
components = this._components;
|
||||
|
||||
this.component((await import(/* webpackChunkName: "vue" */ 'src/std-components/index.js')).default);
|
||||
|
||||
if ( this.root.raven )
|
||||
this.root.raven.addPlugin(RavenVue, Vue);
|
||||
|
||||
for(const key in components)
|
||||
if ( has(components, key) )
|
||||
Vue.component(key, components[key]);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue