mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +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
|
@ -1,3 +1,18 @@
|
|||
<div class="list-header">4.0.0-beta2.6<span>@b85fa005ec1f3929cdd8</span> <time datetime="2018-04-11">(2018-04-11)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Changed: Filter a bunch of errors out from Sentry logging.</li>
|
||||
<li>API Added: <code>module.hasModule(name)</code> method to test if a module is already installed.</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">4.0.0-beta2.5<span>@b3fb24504616675ad2b9</span> <time datetime="2018-04-11">(2018-04-11)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Added: Automatic error reporting using Sentry.</li>
|
||||
<li>Added: Rarity sorting for the Experiments debugging information.</li>
|
||||
<li>Changed: Allow Ctrl-Shift-Clicking emotes to open their information pages.</li>
|
||||
<li>Fixed: <code>deep_copy</code> erroneously thinking some objects were recursive.</li>
|
||||
<li>Fixed: The option to disallow mouse mouse interaction with extensions was not functioning.</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">4.0.0-beta2.4<span>@b3fb24504616675ad2b9</span> <time datetime="2018-04-10">(2018-04-10)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Added: Debugging > Experiments for viewing active experiment information.</li>
|
||||
|
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -7294,6 +7294,11 @@
|
|||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
|
||||
"dev": true
|
||||
},
|
||||
"raven-js": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.24.1.tgz",
|
||||
"integrity": "sha512-p+e+yoQbq4YgXDonYIRZNL/Kov6+t5L0UNEHZeYNzjOkNNCXcwQ1Vi3pulgGBaOjqXNipkFsbpmnH7YI+GPSjw=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"js-cookie": "^2.2.0",
|
||||
"path-to-regexp": "^2.2.0",
|
||||
"popper.js": "^1.14.2",
|
||||
"raven-js": "^3.24.1",
|
||||
"sortablejs": "^1.7.0",
|
||||
"vue": "^2.5.16",
|
||||
"vue-clickaway": "^2.1.0",
|
||||
|
|
Binary file not shown.
|
@ -76,6 +76,14 @@
|
|||
|
||||
<glyph glyph-name="arrows-cw" unicode="" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="ignore" unicode="" d="M813 141v-291l-233 194c-26-4-53-6-80-6-242 0-437 153-437 343 0 190 195 344 437 344s438-154 438-344c0-93-48-178-125-240z m-125 272h-375v-63h375v63z" horiz-adv-x="1000" />
|
||||
|
||||
<glyph glyph-name="block" unicode="" d="M732 352q0 90-48 164l-421-420q76-50 166-50 62 0 118 25t96 65 65 97 24 119z m-557-167l421 421q-75 50-167 50-83 0-153-40t-110-111-41-153q0-91 50-167z m682 167q0-88-34-168t-91-137-137-92-166-34-167 34-137 92-91 137-34 168 34 167 91 137 137 91 167 34 166-34 137-91 91-137 34-167z" horiz-adv-x="857.1" />
|
||||
|
||||
<glyph glyph-name="pin" unicode="" d="M573 37q0-23-15-38t-37-15q-21 0-37 16l-169 169-315-236 236 315-168 169q-24 23-12 56 14 32 48 32 157 0 270 57 90 45 151 171 9 24 36 32t50-13l208-209q21-23 14-50t-32-36q-127-63-172-152-56-110-56-268z" horiz-adv-x="834" />
|
||||
|
||||
<glyph glyph-name="pin-outline" unicode="" d="M856 554q30-30 30-73t-30-75q-16-16-36-24-106-51-144-125-51-102-51-246 0-45-29-74t-75-30q-47 0-75 30l-167 169-279-199 199 279-169 168q-20 18-27 49t5 64q27 65 96 65 146 0 246 50l11 7q69 37 118 141 7 20 21 34 30 30 74 29t74-31z m-273-250q53 107 196 175l-205 211q-69-146-176-200l-13-6q-120-57-281-57l416-416q0 168 63 293z" horiz-adv-x="886" />
|
||||
|
||||
<glyph glyph-name="twitter" unicode="" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
|
||||
|
||||
<glyph glyph-name="gauge" unicode="" d="M214 207q0 30-21 51t-50 21-51-21-21-51 21-50 51-21 50 21 21 50z m107 250q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m239-268l57 213q3 14-5 27t-21 16-27-3-17-22l-56-213q-33-3-60-25t-35-55q-11-43 11-81t66-50 81 11 50 66q9 33-4 65t-40 51z m369 18q0 30-21 51t-51 21-50-21-21-51 21-50 50-21 51 21 21 50z m-358 357q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m250-107q0 30-20 51t-51 21-50-21-21-51 21-50 50-21 51 21 20 50z m179-250q0-145-79-269-10-17-30-17h-782q-20 0-30 17-79 123-79 269 0 102 40 194t106 160 160 107 194 39 194-39 160-107 106-160 40-194z" horiz-adv-x="1000" />
|
||||
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -18,6 +18,12 @@ const OVERRIDE_COOKIE = 'experiment_overrides',
|
|||
};
|
||||
|
||||
|
||||
// We want to import this so that the file is included in the output.
|
||||
// We don't load using this because we might want a newer file from the
|
||||
// server.
|
||||
import EXPERIMENTS from 'file-loader?name=[name].[hash].[ext]!./experiments.json'; // eslint-disable-line no-unused-vars
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Experiment Manager
|
||||
// ============================================================================
|
||||
|
@ -31,6 +37,9 @@ export default class ExperimentManager extends Module {
|
|||
this.settings.addUI('experiments', {
|
||||
path: 'Debugging > Experiments',
|
||||
component: 'experiments',
|
||||
|
||||
unique_id: () => this.unique_id,
|
||||
|
||||
ffz_data: () => deep_copy(this.experiments),
|
||||
twitch_data: () => deep_copy(this.getTwitchExperiments()),
|
||||
|
||||
|
@ -66,7 +75,7 @@ export default class ExperimentManager extends Module {
|
|||
let data;
|
||||
|
||||
try {
|
||||
data = await fetch(`${SERVER}/static/experiments.json?_=${Date.now()}`).then(r =>
|
||||
data = await fetch(`${SERVER}/script/experiments.json?_=${Date.now()}`).then(r =>
|
||||
r.ok ? r.json() : null);
|
||||
|
||||
} catch(err) {
|
||||
|
@ -179,12 +188,11 @@ export default class ExperimentManager extends Module {
|
|||
|
||||
_rebuildTwitchKey(key, is_set, new_val) {
|
||||
const core = this.resolve('site').getCore(),
|
||||
exps = core.experiments;
|
||||
exps = core.experiments,
|
||||
|
||||
if ( ! has(exps.assignments, key) )
|
||||
return;
|
||||
|
||||
const old_val = exps.assignments[key];
|
||||
old_val = has(exps.assignments, key) ?
|
||||
exps.assignments[key] :
|
||||
undefined;
|
||||
|
||||
if ( old_val !== new_val ) {
|
||||
const value = is_set ? new_val : old_val;
|
||||
|
|
19
src/main.js
19
src/main.js
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import RavenLogger from './raven';
|
||||
|
||||
import Logger from 'utilities/logging';
|
||||
import Module from 'utilities/module';
|
||||
|
||||
|
@ -24,7 +26,14 @@ class FrankerFaceZ extends Module {
|
|||
this.__state = 0;
|
||||
this.__modules.core = this;
|
||||
|
||||
this.log = new Logger(this);
|
||||
// ========================================================================
|
||||
// Error Reporting and Logging
|
||||
// ========================================================================
|
||||
|
||||
//if ( ! DEBUG )
|
||||
this.inject('raven', RavenLogger);
|
||||
|
||||
this.log = new Logger(null, null, null, this.raven);
|
||||
this.core_log = this.log.get('core');
|
||||
|
||||
this.log.info(`FrankerFaceZ v${VER} (build ${VER.build})`);
|
||||
|
@ -86,18 +95,12 @@ class FrankerFaceZ extends Module {
|
|||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
|
||||
/* eslint class-methods-use-this: off */
|
||||
api(...args) {
|
||||
return this._api.create(...args);
|
||||
}
|
||||
}
|
||||
|
||||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-beta2.4',
|
||||
major: 4, minor: 0, revision: 0, extra: '-beta2.7',
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
|
|
|
@ -195,6 +195,35 @@ export default class Emotes extends Module {
|
|||
|
||||
const provider = ds.provider;
|
||||
|
||||
if ( event.shiftKey && this.parent.context.get('chat.click-emotes') ) {
|
||||
let url;
|
||||
|
||||
if ( provider === 'twitch' )
|
||||
url = `https://twitchemotes.com/emotes/${ds.id}`;
|
||||
|
||||
else if ( provider === 'ffz' ) {
|
||||
const emote_set = this.emote_sets[ds.set],
|
||||
emote = emote_set && emote_set.emotes[ds.id];
|
||||
|
||||
if ( ! emote )
|
||||
return;
|
||||
|
||||
if ( emote.click_url )
|
||||
url = emote.click_url;
|
||||
|
||||
else if ( ! emote_set.source )
|
||||
url = `https://www.frankerfacez.com/emoticons/${emote.id}`;
|
||||
}
|
||||
|
||||
if ( url ) {
|
||||
const win = window.open();
|
||||
win.opener = null;
|
||||
win.location = url;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( event[MOD_KEY] ) {
|
||||
// Favoriting Emotes
|
||||
let source, id;
|
||||
|
@ -225,35 +254,6 @@ export default class Emotes extends Module {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( event.shiftKey && this.parent.context.get('chat.click-emotes') ) {
|
||||
let url;
|
||||
|
||||
if ( provider === 'twitch' )
|
||||
url = `https://twitchemotes.com/emotes/${ds.id}`;
|
||||
|
||||
else if ( provider === 'ffz' ) {
|
||||
const emote_set = this.emote_sets[ds.set],
|
||||
emote = emote_set && emote_set.emotes[ds.id];
|
||||
|
||||
if ( ! emote )
|
||||
return;
|
||||
|
||||
if ( emote.click_url )
|
||||
url = emote.click_url;
|
||||
|
||||
else if ( ! emote_set.source )
|
||||
url = `https://www.frankerfacez.com/emoticons/${emote.id}`;
|
||||
}
|
||||
|
||||
if ( url ) {
|
||||
const win = window.open();
|
||||
win.opener = null;
|
||||
win.location = url;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -303,11 +303,15 @@ export default class Chat extends Module {
|
|||
else if ( this.users[login] && ! no_login )
|
||||
user = this.users[login];
|
||||
|
||||
else if ( no_create )
|
||||
return null;
|
||||
if ( user && user.destroyed )
|
||||
user = null;
|
||||
|
||||
else
|
||||
user = new User(this, null, id, login);
|
||||
if ( ! user ) {
|
||||
if ( no_create )
|
||||
return null;
|
||||
else
|
||||
user = new User(this, null, id, login);
|
||||
}
|
||||
|
||||
if ( id && id !== user.id ) {
|
||||
// If the ID isn't what we expected, something is very wrong here.
|
||||
|
@ -358,11 +362,15 @@ export default class Chat extends Module {
|
|||
else if ( this.rooms[login] && ! no_login )
|
||||
room = this.rooms[login];
|
||||
|
||||
else if ( no_create )
|
||||
return null;
|
||||
if ( room && room.destroyed )
|
||||
room = null;
|
||||
|
||||
else
|
||||
room = new Room(this, id, login);
|
||||
if ( ! room ) {
|
||||
if ( no_create )
|
||||
return null;
|
||||
else
|
||||
room = new Room(this, id, login);
|
||||
}
|
||||
|
||||
if ( id && id !== room.id ) {
|
||||
// If the ID isn't what we expected, something is very wrong here.
|
||||
|
|
|
@ -70,8 +70,8 @@ export default class Room {
|
|||
this.manager.socket.unsubscribe(this, `room.${this.login}`);
|
||||
}
|
||||
|
||||
if ( this.manager.room_ids[this.id] === this )
|
||||
this.manager.room_ids[this.id] = null;
|
||||
if ( this.manager.room_ids[this._id] === this )
|
||||
this.manager.room_ids[this._id] = null;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -28,6 +28,14 @@ export default class User {
|
|||
this.manager.emotes.unrefSet(set_id);
|
||||
|
||||
this.emote_sets = null;
|
||||
|
||||
const parent = this.room || this.manager;
|
||||
|
||||
if ( this._login && parent.users[this._login] === this )
|
||||
parent.users[this._login] = null;
|
||||
|
||||
if ( parent.user_ids[this._id] === this )
|
||||
parent.user_ids[this._id] = null;
|
||||
}
|
||||
|
||||
get id() {
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--experiments tw-pd-t-05">
|
||||
<div class="tw-pd-b-1 tw-mg-b-2 tw-border-b">
|
||||
<div class="tw-pd-b-1 tw-mg-b-1 tw-border-b">
|
||||
{{ t('settings.experiments.about', 'This feature allows you to override experiment values. Please note that, for most experiments, you may have to refresh the page for your changes to take effect.') }}
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1">
|
||||
{{ t('settings.experiments.unique-id', 'Unique ID: %{id}', {id: unique_id}) }}
|
||||
</div>
|
||||
<select
|
||||
ref="sort_select"
|
||||
class="tw-mg-x-05 tw-select tw-display-line tw-width-auto"
|
||||
@change="onSort"
|
||||
>
|
||||
<option :selected="sort_by === 0">{{ t('settings.experiments.sort-name', 'Sort By: Name') }}</option>
|
||||
<option :selected="sort_by === 1">{{ t('settings.experiments.sort-rarity', 'Sort By: Rarity') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h3 class="tw-mg-b-1">
|
||||
{{ t('settings.experiments.ffz', 'FrankerFaceZ Experiments') }}
|
||||
</h3>
|
||||
|
@ -34,7 +48,7 @@
|
|||
:key="idx"
|
||||
:selected="i.value === exp.value"
|
||||
>
|
||||
{{ t('settings.exepriments.entry', '%{value} (weight: %{weight})', i) }}
|
||||
{{ t('settings.experiments.entry', '%{value} (weight: %{weight})', i) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
|
@ -97,6 +111,12 @@
|
|||
class="tw-mg-05 tw-select tw-display-line tw-width-auto"
|
||||
@change="onTwitchChange($event)"
|
||||
>
|
||||
<option
|
||||
v-if="exp.in_use === false"
|
||||
:selected="exp.default"
|
||||
>
|
||||
{{ t('settings.experiments.unset', 'unset') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(i, idx) in exp.groups"
|
||||
:key="idx"
|
||||
|
@ -136,6 +156,8 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
sort_by: 1,
|
||||
unique_id: this.item.unique_id(),
|
||||
ffz_data: this.item.ffz_data(),
|
||||
twitch_data: this.item.twitch_data()
|
||||
}
|
||||
|
@ -147,6 +169,10 @@ export default {
|
|||
const exp = this.ffz_data[key];
|
||||
this.$set(exp, 'value', this.item.getAssignment(key));
|
||||
this.$set(exp, 'default', ! this.item.hasOverride(key));
|
||||
|
||||
exp.total = exp.groups.reduce((a,b) => a + b.weight, 0);
|
||||
this.calculateRarity(exp);
|
||||
|
||||
}
|
||||
|
||||
for(const key in this.twitch_data)
|
||||
|
@ -157,6 +183,8 @@ export default {
|
|||
|
||||
exp.in_use = this.item.usingTwitchExperiment(key);
|
||||
exp.remainder = `v: ${exp.v}, t: ${exp.t}`;
|
||||
exp.total = exp.groups.reduce((a,b) => a + b.weight, 0);
|
||||
this.calculateRarity(exp);
|
||||
}
|
||||
|
||||
this.item.on(':changed', this.valueChanged, this);
|
||||
|
@ -169,6 +197,17 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
calculateRarity(exp) {
|
||||
let rarity;
|
||||
for(const group of exp.groups)
|
||||
if ( group.value === exp.value ) {
|
||||
rarity = group.weight / exp.total;
|
||||
break;
|
||||
}
|
||||
|
||||
this.$set(exp, 'rarity', rarity);
|
||||
},
|
||||
|
||||
sorted(data) {
|
||||
const out = Object.entries(data).map(x => ({key: x[0], exp: x[1]}));
|
||||
|
||||
|
@ -179,11 +218,20 @@ export default {
|
|||
if ( a_use && ! b_use ) return -1;
|
||||
if ( ! a_use && b_use ) return 1;
|
||||
|
||||
if ( this.sort_by === 1 ) {
|
||||
const a_r = a.exp.rarity,
|
||||
b_r = b.exp.rarity;
|
||||
|
||||
if ( a_r < b_r ) return -1;
|
||||
if ( a_r > b_r ) return 1;
|
||||
}
|
||||
|
||||
const a_n = a.exp.name.toLowerCase(),
|
||||
b_n = b.exp.name.toLowerCase();
|
||||
|
||||
if ( a_n < b_n ) return -1;
|
||||
if ( a_n > b_n ) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
|
@ -204,6 +252,10 @@ export default {
|
|||
exp.default = ! this.item.hasTwitchOverride(key);
|
||||
},
|
||||
|
||||
onSort() {
|
||||
this.sort_by = this.$refs.sort_select.selectedIndex;
|
||||
},
|
||||
|
||||
onChange(event) {
|
||||
const el = event.target,
|
||||
idx = el.selectedIndex,
|
||||
|
@ -223,8 +275,9 @@ export default {
|
|||
key = el.dataset.key;
|
||||
|
||||
const exp = this.twitch_data[key],
|
||||
offset = exp.in_use ? 0 : 1,
|
||||
groups = exp && exp.groups,
|
||||
entry = groups && groups[idx];
|
||||
entry = groups && groups[idx - offset];
|
||||
|
||||
if ( entry )
|
||||
this.item.setTwitchOverride(key, entry.value);
|
||||
|
@ -235,6 +288,7 @@ export default {
|
|||
if ( exp ) {
|
||||
exp.value = value;
|
||||
exp.default = ! this.item.hasOverride(key);
|
||||
this.calculateRarity(exp);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -243,6 +297,7 @@ export default {
|
|||
if ( exp ) {
|
||||
exp.value = value;
|
||||
exp.default = ! this.item.hasTwitchOverride(key);
|
||||
this.calculateRarity(exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -439,6 +439,11 @@ export default class Metadata extends Module {
|
|||
el.disabled = maybe_call(def.disabled, this, data);
|
||||
|
||||
} catch(err) {
|
||||
this.log.capture(err, {
|
||||
tags: {
|
||||
metadata: key
|
||||
}
|
||||
});
|
||||
this.log.error(`Error rendering metadata for ${key}`, err);
|
||||
return destroy();
|
||||
}
|
||||
|
|
174
src/raven.js
Normal file
174
src/raven.js
Normal file
|
@ -0,0 +1,174 @@
|
|||
'use strict';
|
||||
|
||||
/* global FrankerFaceZ: false */
|
||||
|
||||
// ============================================================================
|
||||
// Raven Logging
|
||||
// ============================================================================
|
||||
|
||||
import {DEBUG, SENTRY_ID} from 'utilities/constants';
|
||||
import {has} from 'utilities/object';
|
||||
import Module from 'utilities/module';
|
||||
|
||||
import Raven from 'raven-js';
|
||||
|
||||
const BAD_URLS = [
|
||||
'hls.ttvnw.net',
|
||||
'trowel.twitch.tv',
|
||||
'client-event-reporter.twitch.tv',
|
||||
'.twitch.tv/gql',
|
||||
'spade.twitch.tv'
|
||||
];
|
||||
|
||||
const BAD_QUERIES = [
|
||||
'ChannelPage_SetSessionStatus'
|
||||
];
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Raven Logger
|
||||
// ============================================================================
|
||||
|
||||
export default class RavenLogger extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject('settings');
|
||||
this.inject('site');
|
||||
this.inject('experiments');
|
||||
|
||||
this.raven = Raven;
|
||||
|
||||
Raven.config(SENTRY_ID, {
|
||||
autoBreadcrumbs: {
|
||||
console: false
|
||||
},
|
||||
release: FrankerFaceZ.version_info.toString(),
|
||||
environment: DEBUG ? 'development' : 'production',
|
||||
captureUnhandledRejections: false,
|
||||
ignoreErrors: [
|
||||
'InvalidAccessError',
|
||||
'out of memory'
|
||||
],
|
||||
whitelistUrls: [
|
||||
/cdn\.frankerfacez\.com/
|
||||
],
|
||||
sanitizeKeys: [
|
||||
/Token$/
|
||||
],
|
||||
breadcrumbCallback(crumb) {
|
||||
if ( crumb.category === 'gql' ) {
|
||||
for(const matcher of BAD_QUERIES)
|
||||
if ( crumb.message.includes(matcher) )
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( crumb.type === 'http' ) {
|
||||
const url = crumb.data.url;
|
||||
for(const matcher of BAD_URLS)
|
||||
if ( url.includes(matcher) )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
shouldSendCallback(data) {
|
||||
if ( data.message && data.messages.includes('raven-js/') )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}).install();
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
const user = this.site.getUser();
|
||||
if ( user )
|
||||
this.raven.setUserContext({
|
||||
id: user.id,
|
||||
username: user.login
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
buildExtra() {
|
||||
const context = this.settings.main_context,
|
||||
chat_context = this.resolve('chat').context;
|
||||
|
||||
|
||||
const settings = {},
|
||||
chat_settings = {},
|
||||
modules = {},
|
||||
experiments = {},
|
||||
twitch_experiments = {},
|
||||
out = {
|
||||
experiments,
|
||||
twitch_experiments,
|
||||
modules,
|
||||
settings,
|
||||
settings_context: context._context,
|
||||
chat_settings,
|
||||
chat_context: chat_context._context
|
||||
};
|
||||
|
||||
for(const key in this.__modules)
|
||||
if ( has(this.__modules, key) ) {
|
||||
const mod = this.__modules[key];
|
||||
modules[key] = [
|
||||
mod.loaded ? 'loaded' : mod.loading ? 'loading' : 'unloaded',
|
||||
mod.enabled ? 'enabled' : mod.enabling ? 'enabling' : 'disabled'
|
||||
]
|
||||
}
|
||||
|
||||
for(const [key, value] of context.__cache.entries())
|
||||
settings[key] = value;
|
||||
|
||||
for(const [key, value] of chat_context.__cache.entries())
|
||||
chat_settings[key] = value;
|
||||
|
||||
for(const [key, value] of Object.entries(this.experiments.getTwitchExperiments()))
|
||||
if ( this.experiments.usingTwitchExperiment(key) )
|
||||
twitch_experiments[value.name] = this.experiments.getTwitchAssignment(key);
|
||||
|
||||
for(const key of Object.keys(this.experiments.experiments))
|
||||
experiments[key] = this.experiments.getAssignment(key);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
buildTags() {
|
||||
const core = this.site.getCore(),
|
||||
out = {};
|
||||
|
||||
out.build = __webpack_hash__;
|
||||
|
||||
if ( core )
|
||||
out.twitch_build = core.config.buildID;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
addPlugin(...args) { return this.raven.addPlugin(...args) }
|
||||
setUserContext(...args) { return this.raven.setUserContext(...args) }
|
||||
|
||||
captureException(exc, opts) {
|
||||
opts = opts || {};
|
||||
opts.extra = Object.assign(this.buildExtra(), opts.extra);
|
||||
opts.tags = Object.assign(this.buildTags(), opts.tags);
|
||||
|
||||
return this.raven.captureException(exc, opts);
|
||||
}
|
||||
|
||||
captureMessage(msg, opts) {
|
||||
opts = opts || {};
|
||||
opts.extra = Object.assign(this.buildExtra(), opts.extra);
|
||||
opts.tags = Object.assign(this.buildTags(), opts.tags);
|
||||
|
||||
return this.raven.captureMessage(msg, opts);
|
||||
}
|
||||
|
||||
captureBreadcrumb(...args) { return this.raven.captureBreadcrumb(...args) }
|
||||
}
|
|
@ -516,6 +516,7 @@ export default class ChatHook extends Module {
|
|||
return ret;
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err, {extra: e});
|
||||
return old_resub.call(i, e);
|
||||
}
|
||||
}
|
||||
|
@ -533,6 +534,7 @@ export default class ChatHook extends Module {
|
|||
return ret;
|
||||
|
||||
} catch(err) {
|
||||
t.log.capture(err, {extra: e});
|
||||
return old_ritual.call(i, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,9 @@ export default class RichContent extends Module {
|
|||
}, data));
|
||||
|
||||
} catch(err) {
|
||||
if ( err.message !== 'timeout' )
|
||||
t.log.capture(err);
|
||||
|
||||
this.setState({
|
||||
loaded: true,
|
||||
error: true,
|
||||
|
|
|
@ -79,6 +79,8 @@ export default class Scroller extends Module {
|
|||
ffz_errors: errs + 1,
|
||||
ffz_total_errors: (this.state.ffz_total_errors||0) + 1
|
||||
});
|
||||
|
||||
t.log.capture(err, {extra: info});
|
||||
t.log.info('Error within Chat', err, info, errs);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
.player .extension-overlay__iframe,
|
||||
.player .extension-overlay {
|
||||
pointer-events: none !important;
|
||||
}
|
|
@ -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]);
|
||||
|
|
|
@ -115,6 +115,10 @@
|
|||
.ffz-i-lock:before { content: '\e820'; } /* '' */
|
||||
.ffz-i-lock-open:before { content: '\e821'; } /* '' */
|
||||
.ffz-i-arrows-cw:before { content: '\e822'; } /* '' */
|
||||
.ffz-i-ignore:before { content: '\e823'; } /* '' */
|
||||
.ffz-i-block:before { content: '\e824'; } /* '' */
|
||||
.ffz-i-pin:before { content: '\e825'; } /* '' */
|
||||
.ffz-i-pin-outline:before { content: '\e826'; } /* '' */
|
||||
.ffz-i-twitter:before { content: '\f099'; } /* '' */
|
||||
.ffz-i-gauge:before { content: '\f0e4'; } /* '' */
|
||||
.ffz-i-download-cloud:before { content: '\f0ed'; } /* '' */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue