mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-09-17 02:16:54 +00:00
4.0.0 Beta 1
This commit is contained in:
parent
c2688646af
commit
262757a20d
187 changed files with 22878 additions and 38882 deletions
308
src/utilities/compat/apollo.js
Normal file
308
src/utilities/compat/apollo.js
Normal file
|
@ -0,0 +1,308 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Apollo
|
||||
// Legendary Data Access Layer
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
export default class Apollo extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.modifiers = {};
|
||||
this.post_modifiers = {};
|
||||
|
||||
this.inject('..web_munch');
|
||||
this.inject('..fine');
|
||||
|
||||
this.registerModifier('ChannelPage_ChannelInfoBar_User', `query {
|
||||
user {
|
||||
stream {
|
||||
createdAt
|
||||
type
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
this.registerModifier('FollowedIndex_CurrentUser', `query {
|
||||
currentUser {
|
||||
followedLiveUsers {
|
||||
nodes {
|
||||
profileImageURL(width: 70)
|
||||
stream {
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
followedHosts {
|
||||
nodes {
|
||||
hosting {
|
||||
profileImageURL(width: 70)
|
||||
stream {
|
||||
createdAt
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
this.registerModifier('FollowingLive_CurrentUser', `query {
|
||||
currentUser {
|
||||
followedLiveUsers {
|
||||
nodes {
|
||||
profileImageURL(width: 70)
|
||||
stream {
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
this.registerModifier('ViewerCard', `query {
|
||||
targetUser: user {
|
||||
createdAt
|
||||
profileViewCount
|
||||
}
|
||||
}`);
|
||||
|
||||
this.registerModifier('GamePage_Game', `query {
|
||||
game {
|
||||
streams {
|
||||
edges {
|
||||
node {
|
||||
createdAt
|
||||
type
|
||||
broadcaster {
|
||||
profileImageURL(width: 70)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
}
|
||||
|
||||
async onEnable() {
|
||||
// TODO: Come up with a better way to await something existing.
|
||||
let client = this.client,
|
||||
graphql = this.graphql;
|
||||
|
||||
if ( ! client ) {
|
||||
const root = this.fine.getParent(this.fine.react),
|
||||
ctx = root && root._context;
|
||||
|
||||
client = this.client = ctx && ctx.client;
|
||||
}
|
||||
|
||||
if ( ! graphql )
|
||||
graphql = this.graphql = await this.web_munch.findModule('graphql', m => m.parse && m.parseValue);
|
||||
|
||||
if ( ! client || ! graphql )
|
||||
return new Promise(s => setTimeout(s,50)).then(() => this.onEnable());
|
||||
|
||||
// Parse the queries for modifiers that were already registered.
|
||||
for(const key in this.modifiers)
|
||||
if ( has(this.modifiers, key) ) {
|
||||
const modifiers = this.modifiers[key];
|
||||
if ( modifiers )
|
||||
for(const mod of modifiers) {
|
||||
if ( typeof mod === 'function' || mod[1] === false )
|
||||
continue;
|
||||
|
||||
try {
|
||||
mod[1] = graphql.parse(mod[0], {noLocation: true});
|
||||
} catch(err) {
|
||||
this.log.error(`Error parsing GraphQL statement for "${key}" modifier.`, err);
|
||||
mod[1] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register middleware so that we can intercept requests.
|
||||
this.client.networkInterface.use([{
|
||||
applyBatchMiddleware: (req, next) => {
|
||||
if ( this.enabled )
|
||||
this.apolloPreFlight(req);
|
||||
|
||||
next();
|
||||
}
|
||||
}]);
|
||||
|
||||
this.client.networkInterface.useAfter([{
|
||||
applyBatchAfterware: (resp, next) => {
|
||||
if ( this.enabled )
|
||||
this.apolloPostFlight(resp);
|
||||
|
||||
next();
|
||||
}
|
||||
}]);
|
||||
}
|
||||
|
||||
|
||||
onDisable() {
|
||||
// TODO: Remove Apollo middleware.
|
||||
|
||||
// Tear down the parsed queries.
|
||||
for(const key in this.modifiers)
|
||||
if ( has(this.modifiers, key) ) {
|
||||
const modifiers = this.modifiers[key];
|
||||
if ( modifiers )
|
||||
for(const mod of modifiers) {
|
||||
if ( typeof mod === 'function' )
|
||||
continue;
|
||||
|
||||
mod[1] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// And finally, remove our references.
|
||||
this.client = this.graphql = null;
|
||||
}
|
||||
|
||||
|
||||
apolloPreFlight(request) {
|
||||
for(const req of request.requests) {
|
||||
const operation = req.operationName,
|
||||
modifiers = this.modifiers[operation];
|
||||
|
||||
if ( modifiers )
|
||||
for(const mod of modifiers) {
|
||||
if ( typeof mod === 'function' )
|
||||
mod(req);
|
||||
else if ( mod[1] )
|
||||
this.applyModifier(req, mod[1]);
|
||||
}
|
||||
|
||||
this.emit(`:request.${operation}`, req.query, req.variables);
|
||||
}
|
||||
}
|
||||
|
||||
apolloPostFlight(response) {
|
||||
for(const resp of response.responses) {
|
||||
const operation = resp.extensions.operationName,
|
||||
modifiers = this.post_modifiers[operation];
|
||||
|
||||
if ( modifiers )
|
||||
for(const mod of modifiers)
|
||||
mod(resp);
|
||||
|
||||
this.emit(`:response.${operation}`, resp.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
applyModifier(request, modifier) { // eslint-disable-line class-methods-use-this
|
||||
request.query = merge(request.query, modifier);
|
||||
}
|
||||
|
||||
|
||||
registerModifier(operation, modifier) {
|
||||
if ( typeof modifier !== 'function' ) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = this.graphql ? this.graphql.parse(modifier, {noLocation: true}) : null;
|
||||
} catch(err) {
|
||||
this.log.error(`Error parsing GraphQL statement for "${operation}" modifier.`, err);
|
||||
parsed = false;
|
||||
}
|
||||
|
||||
modifier = [modifier, parsed];
|
||||
}
|
||||
|
||||
const mods = this.modifiers[operation] = this.modifiers[operation] || [];
|
||||
mods.push(modifier);
|
||||
}
|
||||
|
||||
unregisterModifier(operation, modifier) {
|
||||
const mods = this.modifiers[operation];
|
||||
if ( ! mods )
|
||||
return;
|
||||
|
||||
for(let i=0; i < mods.length; i++) {
|
||||
const mod = mods[i];
|
||||
if ( typeof mod === 'function' ? mod === modifier : mod[0] === modifier ) {
|
||||
mods.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Querying
|
||||
// ========================================================================
|
||||
|
||||
getQuery(operation) {
|
||||
const qm = this.client.queryManager,
|
||||
name_map = qm && qm.queryIdsByName,
|
||||
query_map = qm && qm.observableQueries,
|
||||
query_id = name_map && name_map[operation],
|
||||
query = query_map && query_map[query_id];
|
||||
|
||||
return query && query.observableQuery;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Query Merging
|
||||
// ============================================================================
|
||||
|
||||
function canMerge(a, b) {
|
||||
return a.kind === b.kind &&
|
||||
a.kind !== 'FragmentDefinition' &&
|
||||
(a.selectionSet == null) === (b.selectionSet == null);
|
||||
}
|
||||
|
||||
|
||||
function merge(a, b) {
|
||||
if ( ! canMerge(a, b) )
|
||||
return a;
|
||||
|
||||
if ( a.definitions ) {
|
||||
const a_def = a.definitions,
|
||||
b_def = b.definitions;
|
||||
|
||||
for(let i=0; i < a_def.length && i < b_def.length; i++)
|
||||
a_def[i] = merge(a_def[i], b_def[i]);
|
||||
}
|
||||
|
||||
if ( a.selectionSet ) {
|
||||
const s = a.selectionSet.selections,
|
||||
selects = {};
|
||||
for(const sel of b.selectionSet.selections)
|
||||
selects[`${sel.name.value}:${sel.alias?sel.alias.value:null}`] = sel;
|
||||
|
||||
for(let i=0, l = s.length; i < l; i++) {
|
||||
const sel = s[i],
|
||||
name = sel.name.value,
|
||||
alias = sel.alias ? sel.alias.value : null,
|
||||
key = `${name}:${alias}`,
|
||||
other = selects[key];
|
||||
|
||||
if ( other ) {
|
||||
s[i] = merge(sel, other);
|
||||
selects[key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
for(const key in selects)
|
||||
if ( has(selects, key) ) {
|
||||
const val = selects[key];
|
||||
if ( val )
|
||||
s.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Variables?
|
||||
|
||||
return a;
|
||||
}
|
87
src/utilities/compat/fine-router.js
Normal file
87
src/utilities/compat/fine-router.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Fine Router
|
||||
// ============================================================================
|
||||
|
||||
import {parse, tokensToRegExp, tokensToFunction} from 'path-to-regexp';
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
|
||||
export default class FineRouter extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.inject('..fine');
|
||||
|
||||
this.__routes = [];
|
||||
this.routes = {};
|
||||
this.current = null;
|
||||
this.match = null;
|
||||
this.location = null;
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
const root = this.fine.getParent(this.fine.react),
|
||||
ctx = this.context = root && root._context,
|
||||
router = ctx && ctx.router,
|
||||
history = router && router.history;
|
||||
|
||||
if ( ! history )
|
||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
||||
|
||||
history.listen(location => {
|
||||
if ( this.enabled )
|
||||
this._navigateTo(location);
|
||||
});
|
||||
|
||||
this._navigateTo(history.location);
|
||||
}
|
||||
|
||||
_navigateTo(location) {
|
||||
this.log.debug('New Location', location);
|
||||
const path = location.pathname;
|
||||
if ( path === this.location )
|
||||
return;
|
||||
|
||||
this.location = path;
|
||||
|
||||
for(const route of this.__routes) {
|
||||
const match = route.regex.exec(path);
|
||||
if ( match ) {
|
||||
this.log.debug('Matching Route', route, match);
|
||||
this.current = route;
|
||||
this.match = match;
|
||||
this.emit(':route', route, match);
|
||||
this.emit(`:route:${route.name}`, ...match);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.current = this.match = null;
|
||||
this.emit(':route', null, null);
|
||||
}
|
||||
|
||||
route(name, path) {
|
||||
if ( typeof name === 'object' ) {
|
||||
for(const key in name)
|
||||
if ( has(name, key) )
|
||||
this.route(key, name[key]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = parse(path),
|
||||
score = parts.length,
|
||||
route = this.routes[name] = {
|
||||
name,
|
||||
parts,
|
||||
score,
|
||||
regex: tokensToRegExp(parts),
|
||||
url: tokensToFunction(parts)
|
||||
}
|
||||
|
||||
this.__routes.push(route);
|
||||
this.__routes.sort(r => r.score);
|
||||
}
|
||||
}
|
486
src/utilities/compat/fine.js
Normal file
486
src/utilities/compat/fine.js
Normal file
|
@ -0,0 +1,486 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Fine Lib
|
||||
// It controls React.
|
||||
// ============================================================================
|
||||
|
||||
import {EventEmitter} from 'utilities/events';
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
|
||||
export default class Fine extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this._wrappers = new Map;
|
||||
this._known_classes = new Map;
|
||||
this._observer = null;
|
||||
this._waiting = null;
|
||||
}
|
||||
|
||||
|
||||
async onEnable() {
|
||||
// TODO: Move awaitElement to utilities/dom
|
||||
if ( ! this.root_element )
|
||||
this.root_element = await this.parent.awaitElement(this.selector || '[data-reactroot]');
|
||||
|
||||
const accessor = this.accessor = Fine.findAccessor(this.root_element);
|
||||
if ( ! accessor )
|
||||
return new Promise(r => setTimeout(r, 50)).then(() => this.onEnable());
|
||||
|
||||
this.react = this.getReactInstance(this.root_element);
|
||||
}
|
||||
|
||||
onDisable() {
|
||||
this.root_element = this.react = this.accessor = null;
|
||||
}
|
||||
|
||||
|
||||
static findAccessor(element) {
|
||||
for(const key in element)
|
||||
if ( key.startsWith('__reactInternalInstance$') )
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Low Level Accessors
|
||||
// ========================================================================
|
||||
|
||||
getReactInstance(element) {
|
||||
return element[this.accessor];
|
||||
}
|
||||
|
||||
getOwner(instance) {
|
||||
if ( instance._reactInternalInstance )
|
||||
instance = instance._reactInternalInstance;
|
||||
else if ( instance instanceof Node )
|
||||
instance = this.getReactInstance(instance);
|
||||
|
||||
if ( ! instance )
|
||||
return null;
|
||||
|
||||
return instance._owner || (instance._currentElement && instance._currentElement._owner);
|
||||
}
|
||||
|
||||
getHostNode(instance) { //eslint-disable-line class-methods-use-this
|
||||
if ( instance._reactInternalInstance )
|
||||
instance = instance._reactInternalInstance;
|
||||
else if ( instance instanceof Node )
|
||||
instance = this.getReactInstance(instance);
|
||||
|
||||
while( instance )
|
||||
if ( instance._hostNode )
|
||||
return instance._hostNode;
|
||||
else
|
||||
instance = instance._renderedComponent;
|
||||
}
|
||||
|
||||
getParent(instance) {
|
||||
const owner = this.getOwner(instance);
|
||||
return owner && this.getOwner(owner);
|
||||
}
|
||||
|
||||
searchParent(node, criteria, max_depth=15, depth=0) {
|
||||
if ( node._reactInternalInstance )
|
||||
node = node._reactInternalInstance;
|
||||
else if ( node instanceof Node )
|
||||
node = this.getReactInstance(node);
|
||||
|
||||
if ( ! node || depth > max_depth )
|
||||
return null;
|
||||
|
||||
const inst = node._instance;
|
||||
if ( inst && criteria(inst) )
|
||||
return inst;
|
||||
|
||||
if ( node._currentElement && node._currentElement._owner ) {
|
||||
const result = this.searchParent(node._currentElement._owner, criteria, max_depth, depth+1);
|
||||
if ( result )
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( node._hostParent )
|
||||
return this.searchParent(node._hostParent, criteria, max_depth, depth+1);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
searchTree(node, criteria, max_depth=15, depth=0) {
|
||||
if ( ! node )
|
||||
node = this.react;
|
||||
else if ( node._reactInternalInstance )
|
||||
node = node._reactInternalInstance;
|
||||
else if ( node instanceof Node )
|
||||
node = this.getReactInstance(node);
|
||||
|
||||
if ( ! node || depth > max_depth )
|
||||
return null;
|
||||
|
||||
const inst = node._instance;
|
||||
if ( inst && criteria(inst) )
|
||||
return inst;
|
||||
|
||||
const children = node._renderedChildren,
|
||||
component = node._renderedComponent;
|
||||
|
||||
if ( children )
|
||||
for(const key in children)
|
||||
if ( has(children, key) ) {
|
||||
const child = children[key];
|
||||
const result = child && this.searchTree(child, criteria, max_depth, depth+1);
|
||||
if ( result )
|
||||
return result;
|
||||
}
|
||||
|
||||
if ( component )
|
||||
return this.searchTree(component, criteria, max_depth, depth+1);
|
||||
}
|
||||
|
||||
|
||||
searchAll(node, criterias, max_depth=15, depth=0, data) {
|
||||
if ( ! node )
|
||||
node = this.react;
|
||||
else if ( node._reactInternalInstance )
|
||||
node = node._reactInternalInstance;
|
||||
else if ( node instanceof Node )
|
||||
node = this.getReactInstance(node);
|
||||
|
||||
if ( ! data )
|
||||
data = {
|
||||
seen: new Set,
|
||||
classes: criterias.map(() => null),
|
||||
out: criterias.map(() => ({
|
||||
cls: null, instances: new Set, depth: null
|
||||
})),
|
||||
max_depth: depth
|
||||
};
|
||||
|
||||
if ( ! node || depth > max_depth )
|
||||
return data.out;
|
||||
|
||||
if ( depth > data.max_depth )
|
||||
data.max_depth = depth;
|
||||
|
||||
const inst = node._instance;
|
||||
if ( inst ) {
|
||||
const cls = inst.constructor,
|
||||
idx = data.classes.indexOf(cls);
|
||||
|
||||
if ( idx !== -1 )
|
||||
data.out[idx].instances.add(inst);
|
||||
|
||||
else if ( ! data.seen.has(cls) ) {
|
||||
let i = criterias.length;
|
||||
while(i-- > 0)
|
||||
if ( criterias[i](inst) ) {
|
||||
data.classes[i] = data.out[i].cls = cls;
|
||||
data.out[i].instances.add(inst);
|
||||
data.out[i].depth = depth;
|
||||
break;
|
||||
}
|
||||
|
||||
data.seen.add(cls);
|
||||
}
|
||||
}
|
||||
|
||||
const children = node._renderedChildren,
|
||||
component = node._renderedComponent;
|
||||
|
||||
if ( children )
|
||||
for(const key in children)
|
||||
if ( has(children, key) ) {
|
||||
const child = children[key];
|
||||
child && this.searchAll(child, criterias, max_depth, depth+1, data);
|
||||
}
|
||||
|
||||
if ( component )
|
||||
this.searchAll(component, criterias, max_depth, depth+1, data);
|
||||
|
||||
return data.out;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Class Wrapping
|
||||
// ========================================================================
|
||||
|
||||
define(key, criteria) {
|
||||
if ( this._wrappers.has(key) )
|
||||
return this._wrappers.get(key);
|
||||
|
||||
if ( ! criteria )
|
||||
throw new Error('cannot find definition and no criteria provided');
|
||||
|
||||
const wrapper = new FineWrapper(key, criteria, this);
|
||||
this._wrappers.set(key, wrapper);
|
||||
|
||||
const data = this.searchAll(this.react, [criteria], 1000)[0];
|
||||
if ( data.cls ) {
|
||||
wrapper._set(data.cls, data.instances);
|
||||
this._known_classes.set(data.cls, wrapper);
|
||||
|
||||
} else {
|
||||
if ( ! this._waiting )
|
||||
this._startWaiting();
|
||||
|
||||
this._waiting.push(wrapper);
|
||||
this._waiting_crit.push(criteria);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
|
||||
_checkWaiters(nodes) {
|
||||
if ( ! this._waiting )
|
||||
return;
|
||||
|
||||
if ( ! Array.isArray(nodes) )
|
||||
nodes = [nodes];
|
||||
|
||||
for(let node of nodes) {
|
||||
if ( ! node )
|
||||
node = this.react;
|
||||
else if ( node._reactInternalInstance )
|
||||
node = node._reactInternalInstance;
|
||||
else if ( node instanceof Node )
|
||||
node = this.getReactInstance(node);
|
||||
|
||||
if ( ! node || ! this._waiting.length )
|
||||
continue;
|
||||
|
||||
const data = this.searchAll(node, this._waiting_crit, 1000);
|
||||
let i = data.length;
|
||||
while(i-- > 0) {
|
||||
if ( data[i].cls ) {
|
||||
const d = data[i],
|
||||
w = this._waiting.splice(i, 1)[0];
|
||||
|
||||
this._waiting_crit.splice(i, 1);
|
||||
this.log.info(`Found class for "${w.name}" at depth ${d.depth}`, d);
|
||||
|
||||
w._set(d.cls, d.instances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! this._waiting.length )
|
||||
this._stopWaiting();
|
||||
}
|
||||
|
||||
|
||||
_startWaiting() {
|
||||
this.log.info('Installing MutationObserver.');
|
||||
|
||||
this._waiting = [];
|
||||
this._waiting_crit = [];
|
||||
this._waiting_timer = setInterval(() => this._checkWaiters(), 500);
|
||||
|
||||
if ( ! this._observer )
|
||||
this._observer = new MutationObserver(mutations =>
|
||||
this._checkWaiters(mutations.map(x => x.target))
|
||||
);
|
||||
|
||||
this._observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
_stopWaiting() {
|
||||
this.log.info('Stopping MutationObserver.');
|
||||
|
||||
if ( this._observer )
|
||||
this._observer.disconnect();
|
||||
|
||||
if ( this._waiting_timer )
|
||||
clearInterval(this._waiting_timer);
|
||||
|
||||
this._waiting = null;
|
||||
this._waiting_crit = null;
|
||||
this._waiting_timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EVENTS = {
|
||||
'will-mount': 'componentWillMount',
|
||||
mount: 'componentDidMount',
|
||||
render: 'render',
|
||||
'receive-props': 'componentWillReceiveProps',
|
||||
'should-update': 'shouldComponentUpdate',
|
||||
'will-update': 'componentWillUpdate',
|
||||
update: 'componentDidUpdate',
|
||||
unmount: 'componentWillUnmount'
|
||||
}
|
||||
|
||||
|
||||
export class FineWrapper extends EventEmitter {
|
||||
constructor(name, criteria, fine) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.criteria = criteria;
|
||||
this.fine = fine;
|
||||
|
||||
this.instances = new Set;
|
||||
|
||||
this._wrapped = new Set;
|
||||
this._class = null;
|
||||
}
|
||||
|
||||
ready(fn) {
|
||||
if ( this._class )
|
||||
fn(this._class, this.instances);
|
||||
else
|
||||
this.once('set', fn);
|
||||
}
|
||||
|
||||
_set(cls, instances) {
|
||||
if ( this._class )
|
||||
throw new Error('already have a class');
|
||||
|
||||
this._class = cls;
|
||||
this._wrapped.add('componentWillMount');
|
||||
this._wrapped.add('componentWillUnmount');
|
||||
|
||||
const t = this,
|
||||
_instances = this.instances,
|
||||
proto = cls.prototype,
|
||||
o_mount = proto.componentWillMount,
|
||||
o_unmount = proto.componentWillUnmount,
|
||||
|
||||
mount = proto.componentWillMount = o_mount ?
|
||||
function(...args) {
|
||||
this._ffz_mounted = true;
|
||||
_instances.add(this);
|
||||
t.emit('will-mount', this, ...args);
|
||||
return o_mount.apply(this, args);
|
||||
} :
|
||||
function(...args) {
|
||||
this._ffz_mounted = true;
|
||||
_instances.add(this);
|
||||
t.emit('will-mount', this, ...args);
|
||||
},
|
||||
|
||||
unmount = proto.componentWillUnmount = o_unmount ?
|
||||
function(...args) {
|
||||
t.emit('unmount', this, ...args);
|
||||
_instances.delete(this);
|
||||
this._ffz_mounted = false;
|
||||
return o_unmount.apply(this, args);
|
||||
} :
|
||||
function(...args) {
|
||||
t.emit('unmount', this, ...args);
|
||||
_instances.delete(this);
|
||||
this._ffz_mounted = false;
|
||||
};
|
||||
|
||||
this.__componentWillMount = [mount, o_mount];
|
||||
this.__componentWillUnmount = [unmount, o_unmount];
|
||||
|
||||
for(const event of this.events())
|
||||
this._maybeWrap(event);
|
||||
|
||||
if ( instances )
|
||||
for(const inst of instances) {
|
||||
if ( inst._reactInternalInstance && inst._reactInternalInstance._renderedComponent )
|
||||
inst._ffz_mounted = true;
|
||||
_instances.add(inst);
|
||||
}
|
||||
|
||||
this.emit('set', cls, _instances);
|
||||
}
|
||||
|
||||
_add(instances) {
|
||||
for(const inst of instances)
|
||||
this.instances.add(inst);
|
||||
}
|
||||
|
||||
|
||||
_maybeWrap(event) {
|
||||
const key = EVENTS[event];
|
||||
if ( ! this._class || ! key || this._wrapped.has(key) )
|
||||
return;
|
||||
|
||||
this._wrap(event, key);
|
||||
}
|
||||
|
||||
_wrap(event, key) {
|
||||
if ( this._wrapped.has(key) )
|
||||
return;
|
||||
|
||||
const t = this,
|
||||
proto = this._class.prototype,
|
||||
original = proto[key],
|
||||
|
||||
fn = proto[key] = original ?
|
||||
function(...args) {
|
||||
t.emit(event, this, ...args);
|
||||
return original.apply(this, args);
|
||||
} :
|
||||
|
||||
function(...args) {
|
||||
t.emit(event, this, ...args);
|
||||
};
|
||||
|
||||
this[`__${key}`] = [fn, original];
|
||||
}
|
||||
|
||||
_unwrap(key) {
|
||||
if ( ! this._wrapped.has(key) )
|
||||
return;
|
||||
|
||||
const k = `__${key}`,
|
||||
proto = this._class.prototype,
|
||||
[fn, original] = this[k];
|
||||
|
||||
if ( proto[key] !== fn )
|
||||
throw new Error('unable to unwrap -- prototype modified');
|
||||
|
||||
proto[key] = original;
|
||||
this[k] = undefined;
|
||||
this._wrapped.delete(key);
|
||||
}
|
||||
|
||||
|
||||
on(event, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.on(event, fn, ctx);
|
||||
}
|
||||
|
||||
prependOn(event, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.prependOn(event, fn, ctx);
|
||||
}
|
||||
|
||||
once(event, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.once(event, fn, ctx);
|
||||
}
|
||||
|
||||
prependOnce(event, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.prependOnce(event, fn, ctx);
|
||||
}
|
||||
|
||||
many(event, ttl, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.many(event, ttl, fn, ctx);
|
||||
}
|
||||
|
||||
prependMany(event, ttl, fn, ctx) {
|
||||
this._maybeWrap(event);
|
||||
return super.prependMany(event, ttl, fn, ctx);
|
||||
}
|
||||
|
||||
waitFor(event) {
|
||||
this._maybeWrap(event);
|
||||
return super.waitFor(event);
|
||||
}
|
||||
}
|
168
src/utilities/compat/webmunch.js
Normal file
168
src/utilities/compat/webmunch.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// WebMunch
|
||||
// It consumes webpack.
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
|
||||
let last_muncher = 0;
|
||||
|
||||
export default class WebMunch extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this._id = `_ffz$${last_muncher++}`;
|
||||
this._rid = 0;
|
||||
this._original_loader = null;
|
||||
this._known_rules = {};
|
||||
this._require = null;
|
||||
this._module_names = {};
|
||||
this._mod_cache = {};
|
||||
|
||||
this.hookLoader();
|
||||
this.hookRequire();
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Loaded Modules
|
||||
// ========================================================================
|
||||
|
||||
hookLoader(attempts) {
|
||||
if ( this._original_loader )
|
||||
return this.log.warn('Attempted to call hookLoader twice.');
|
||||
|
||||
this._original_loader = window.webpackJsonp;
|
||||
if ( ! this._original_loader ) {
|
||||
if ( attempts > 500 )
|
||||
return this.log.error("Unable to find webpack's loader after two minutes.");
|
||||
|
||||
return setTimeout(this.hookLoader.bind(this, (attempts||0) + 1), 250);
|
||||
}
|
||||
|
||||
this.log.info(`Found and wrapped webpack's loader after ${(attempts||0)*250}ms.`);
|
||||
window.webpackJsonp = this.webpackJsonp.bind(this);
|
||||
}
|
||||
|
||||
webpackJsonp(chunk_ids, modules) {
|
||||
const names = chunk_ids.map(x => this._module_names[x] || x).join(', ');
|
||||
this.log.info(`Twitch Chunk Loaded: ${chunk_ids} (${names})`);
|
||||
this.log.debug(`Modules: ${Object.keys(modules)}`);
|
||||
|
||||
const res = this._original_loader.apply(window, arguments); // eslint-disable-line prefer-rest-params
|
||||
|
||||
this.emit(':loaded', chunk_ids, names, modules);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Finding Modules
|
||||
// ========================================================================
|
||||
|
||||
known(key, predicate) {
|
||||
if ( typeof key === 'object' ) {
|
||||
for(const k in key)
|
||||
if ( has(key, k) )
|
||||
this.known(k, key[k]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this._known_rules[key] = predicate;
|
||||
}
|
||||
|
||||
|
||||
async findModule(key, predicate) {
|
||||
if ( ! this._require )
|
||||
await this.getRequire();
|
||||
|
||||
return this.getModule(key, predicate);
|
||||
}
|
||||
|
||||
|
||||
getModule(key, predicate) {
|
||||
if ( typeof key === 'function' ) {
|
||||
predicate = key;
|
||||
key = null;
|
||||
}
|
||||
|
||||
if ( key && this._mod_cache[key] )
|
||||
return this._mod_cache[key];
|
||||
|
||||
const require = this._require;
|
||||
if ( ! require || ! require.c )
|
||||
return null;
|
||||
|
||||
if ( ! predicate )
|
||||
predicate = this._known_rules[key];
|
||||
|
||||
if ( ! predicate )
|
||||
throw new Error(`no known predicate for locating ${key}`);
|
||||
|
||||
for(const k in require.c)
|
||||
if ( has(require.c, k) ) {
|
||||
const module = require.c[k],
|
||||
mod = module && module.exports;
|
||||
|
||||
if ( mod && predicate(mod) ) {
|
||||
if ( key )
|
||||
this._mod_cache[key] = mod;
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Grabbing Require
|
||||
// ========================================================================
|
||||
|
||||
getRequire() {
|
||||
if ( this._require )
|
||||
return Promise.resolve(this._require);
|
||||
|
||||
return new Promise(resolve => {
|
||||
const id = `${this._id}$${this._rid++}`;
|
||||
(this._original_loader || window.webpackJsonp)(
|
||||
[],
|
||||
{
|
||||
[id]: (module, exports, __webpack_require__) => {
|
||||
resolve(this._require = __webpack_require__);
|
||||
}
|
||||
},
|
||||
[id]
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
async hookRequire() {
|
||||
const start_time = performance.now(),
|
||||
require = await this.getRequire(),
|
||||
time = performance.now() - start_time;
|
||||
|
||||
this.log.info(`require() grabbed in ${time.toFixed(5)}ms.`);
|
||||
|
||||
const loader = require.e && require.e.toString();
|
||||
let modules;
|
||||
if ( loader && loader.indexOf('Loading chunk') !== -1 ) {
|
||||
const data = /({0:.*?})/.exec(loader);
|
||||
if ( data )
|
||||
try {
|
||||
modules = JSON.parse(data[1].replace(/(\d+):/g, '"$1":'))
|
||||
} catch(err) { } // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
if ( modules ) {
|
||||
this._module_names = modules;
|
||||
this.log.info(`Loaded names for ${Object.keys(modules).length} chunks from require().`)
|
||||
} else
|
||||
this.log.warn(`Unable to find chunk names in require().`);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue