This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
11 KiB
406 lines
11 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
* @providesModule MessageQueue |
|
* @flow |
|
* @format |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const ErrorUtils = require('ErrorUtils'); |
|
const Systrace = require('Systrace'); |
|
|
|
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev'); |
|
const invariant = require('fbjs/lib/invariant'); |
|
const stringifySafe = require('stringifySafe'); |
|
|
|
export type SpyData = { |
|
type: number, |
|
module: ?string, |
|
method: string | number, |
|
args: any[], |
|
}; |
|
|
|
const TO_JS = 0; |
|
const TO_NATIVE = 1; |
|
|
|
const MODULE_IDS = 0; |
|
const METHOD_IDS = 1; |
|
const PARAMS = 2; |
|
const MIN_TIME_BETWEEN_FLUSHES_MS = 5; |
|
|
|
// eslint-disable-next-line no-bitwise |
|
const TRACE_TAG_REACT_APPS = 1 << 17; |
|
|
|
const DEBUG_INFO_LIMIT = 32; |
|
|
|
// Work around an initialization order issue |
|
let JSTimers = null; |
|
|
|
class MessageQueue { |
|
_lazyCallableModules: {[key: string]: (void) => Object}; |
|
_queue: [number[], number[], any[], number]; |
|
_successCallbacks: (?Function)[]; |
|
_failureCallbacks: (?Function)[]; |
|
_callID: number; |
|
_inCall: number; |
|
_lastFlush: number; |
|
_eventLoopStartTime: number; |
|
|
|
_debugInfo: {[number]: [number, number]}; |
|
_remoteModuleTable: {[number]: string}; |
|
_remoteMethodTable: {[number]: string[]}; |
|
|
|
__spy: ?(data: SpyData) => void; |
|
|
|
__guard: (() => void) => void; |
|
|
|
constructor(shouldUninstallGlobalErrorHandler: boolean = false) { |
|
this._lazyCallableModules = {}; |
|
this._queue = [[], [], [], 0]; |
|
this._successCallbacks = []; |
|
this._failureCallbacks = []; |
|
this._callID = 0; |
|
this._lastFlush = 0; |
|
this._eventLoopStartTime = new Date().getTime(); |
|
if (shouldUninstallGlobalErrorHandler) { |
|
this.uninstallGlobalErrorHandler(); |
|
} else { |
|
this.installGlobalErrorHandler(); |
|
} |
|
|
|
if (__DEV__) { |
|
this._debugInfo = {}; |
|
this._remoteModuleTable = {}; |
|
this._remoteMethodTable = {}; |
|
} |
|
|
|
(this: any).callFunctionReturnFlushedQueue = this.callFunctionReturnFlushedQueue.bind( |
|
this, |
|
); |
|
(this: any).callFunctionReturnResultAndFlushedQueue = this.callFunctionReturnResultAndFlushedQueue.bind( |
|
this, |
|
); |
|
(this: any).flushedQueue = this.flushedQueue.bind(this); |
|
(this: any).invokeCallbackAndReturnFlushedQueue = this.invokeCallbackAndReturnFlushedQueue.bind( |
|
this, |
|
); |
|
} |
|
|
|
/** |
|
* Public APIs |
|
*/ |
|
|
|
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) { |
|
if (spyOrToggle === true) { |
|
MessageQueue.prototype.__spy = info => { |
|
console.log( |
|
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` + |
|
`${info.module ? info.module + '.' : ''}${info.method}` + |
|
`(${JSON.stringify(info.args)})`, |
|
); |
|
}; |
|
} else if (spyOrToggle === false) { |
|
MessageQueue.prototype.__spy = null; |
|
} else { |
|
MessageQueue.prototype.__spy = spyOrToggle; |
|
} |
|
} |
|
|
|
callFunctionReturnFlushedQueue(module: string, method: string, args: any[]) { |
|
this.__guard(() => { |
|
this.__callFunction(module, method, args); |
|
}); |
|
|
|
return this.flushedQueue(); |
|
} |
|
|
|
callFunctionReturnResultAndFlushedQueue( |
|
module: string, |
|
method: string, |
|
args: any[], |
|
) { |
|
let result; |
|
this.__guard(() => { |
|
result = this.__callFunction(module, method, args); |
|
}); |
|
|
|
return [result, this.flushedQueue()]; |
|
} |
|
|
|
invokeCallbackAndReturnFlushedQueue(cbID: number, args: any[]) { |
|
this.__guard(() => { |
|
this.__invokeCallback(cbID, args); |
|
}); |
|
|
|
return this.flushedQueue(); |
|
} |
|
|
|
flushedQueue() { |
|
this.__guard(() => { |
|
this.__callImmediates(); |
|
}); |
|
|
|
const queue = this._queue; |
|
this._queue = [[], [], [], this._callID]; |
|
return queue[0].length ? queue : null; |
|
} |
|
|
|
getEventLoopRunningTime() { |
|
return new Date().getTime() - this._eventLoopStartTime; |
|
} |
|
|
|
registerCallableModule(name: string, module: Object) { |
|
this._lazyCallableModules[name] = () => module; |
|
} |
|
|
|
registerLazyCallableModule(name: string, factory: void => Object) { |
|
let module: Object; |
|
let getValue: ?(void) => Object = factory; |
|
this._lazyCallableModules[name] = () => { |
|
if (getValue) { |
|
module = getValue(); |
|
getValue = null; |
|
} |
|
return module; |
|
}; |
|
} |
|
|
|
getCallableModule(name: string) { |
|
const getValue = this._lazyCallableModules[name]; |
|
return getValue ? getValue() : null; |
|
} |
|
|
|
enqueueNativeCall( |
|
moduleID: number, |
|
methodID: number, |
|
params: any[], |
|
onFail: ?Function, |
|
onSucc: ?Function, |
|
) { |
|
if (onFail || onSucc) { |
|
if (__DEV__) { |
|
this._debugInfo[this._callID] = [moduleID, methodID]; |
|
if (this._callID > DEBUG_INFO_LIMIT) { |
|
delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT]; |
|
} |
|
} |
|
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit |
|
// to indicate fail (0) or success (1) |
|
// eslint-disable-next-line no-bitwise |
|
onFail && params.push(this._callID << 1); |
|
// eslint-disable-next-line no-bitwise |
|
onSucc && params.push((this._callID << 1) | 1); |
|
this._successCallbacks[this._callID] = onSucc; |
|
this._failureCallbacks[this._callID] = onFail; |
|
} |
|
|
|
if (__DEV__) { |
|
global.nativeTraceBeginAsyncFlow && |
|
global.nativeTraceBeginAsyncFlow( |
|
TRACE_TAG_REACT_APPS, |
|
'native', |
|
this._callID, |
|
); |
|
} |
|
this._callID++; |
|
|
|
this._queue[MODULE_IDS].push(moduleID); |
|
this._queue[METHOD_IDS].push(methodID); |
|
|
|
if (__DEV__) { |
|
// Validate that parameters passed over the bridge are |
|
// folly-convertible. As a special case, if a prop value is a |
|
// function it is permitted here, and special-cased in the |
|
// conversion. |
|
const isValidArgument = val => { |
|
const t = typeof val; |
|
if ( |
|
t === 'undefined' || |
|
t === 'null' || |
|
t === 'boolean' || |
|
t === 'number' || |
|
t === 'string' |
|
) { |
|
return true; |
|
} |
|
if (t === 'function' || t !== 'object') { |
|
return false; |
|
} |
|
if (Array.isArray(val)) { |
|
return val.every(isValidArgument); |
|
} |
|
for (const k in val) { |
|
if (typeof val[k] !== 'function' && !isValidArgument(val[k])) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
}; |
|
|
|
invariant( |
|
isValidArgument(params), |
|
'%s is not usable as a native method argument', |
|
params, |
|
); |
|
|
|
// The params object should not be mutated after being queued |
|
deepFreezeAndThrowOnMutationInDev((params: any)); |
|
} |
|
this._queue[PARAMS].push(params); |
|
|
|
const now = new Date().getTime(); |
|
if ( |
|
global.nativeFlushQueueImmediate && |
|
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS || |
|
this._inCall === 0) |
|
) { |
|
var queue = this._queue; |
|
this._queue = [[], [], [], this._callID]; |
|
this._lastFlush = now; |
|
global.nativeFlushQueueImmediate(queue); |
|
} |
|
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length); |
|
if (__DEV__ && this.__spy && isFinite(moduleID)) { |
|
this.__spy({ |
|
type: TO_NATIVE, |
|
module: this._remoteModuleTable[moduleID], |
|
method: this._remoteMethodTable[moduleID][methodID], |
|
args: params, |
|
}); |
|
} else if (this.__spy) { |
|
this.__spy({ |
|
type: TO_NATIVE, |
|
module: moduleID + '', |
|
method: methodID, |
|
args: params, |
|
}); |
|
} |
|
} |
|
|
|
createDebugLookup(moduleID: number, name: string, methods: string[]) { |
|
if (__DEV__) { |
|
this._remoteModuleTable[moduleID] = name; |
|
this._remoteMethodTable[moduleID] = methods; |
|
} |
|
} |
|
|
|
uninstallGlobalErrorHandler() { |
|
this.__guard = this.__guardUnsafe; |
|
} |
|
|
|
installGlobalErrorHandler() { |
|
this.__guard = this.__guardSafe; |
|
} |
|
|
|
/** |
|
* Private methods |
|
*/ |
|
|
|
// Lets exceptions propagate to be handled by the VM at the origin |
|
__guardUnsafe(fn: () => void) { |
|
this._inCall++; |
|
fn(); |
|
this._inCall--; |
|
} |
|
|
|
__guardSafe(fn: () => void) { |
|
this._inCall++; |
|
try { |
|
fn(); |
|
} catch (error) { |
|
ErrorUtils.reportFatalError(error); |
|
} finally { |
|
this._inCall--; |
|
} |
|
} |
|
|
|
__callImmediates() { |
|
Systrace.beginEvent('JSTimers.callImmediates()'); |
|
if (!JSTimers) { |
|
JSTimers = require('JSTimers'); |
|
} |
|
JSTimers.callImmediates(); |
|
Systrace.endEvent(); |
|
} |
|
|
|
__callFunction(module: string, method: string, args: any[]): any { |
|
this._lastFlush = new Date().getTime(); |
|
this._eventLoopStartTime = this._lastFlush; |
|
Systrace.beginEvent(`${module}.${method}()`); |
|
if (this.__spy) { |
|
this.__spy({type: TO_JS, module, method, args}); |
|
} |
|
const moduleMethods = this.getCallableModule(module); |
|
invariant( |
|
!!moduleMethods, |
|
'Module %s is not a registered callable module (calling %s)', |
|
module, |
|
method, |
|
); |
|
invariant( |
|
!!moduleMethods[method], |
|
'Method %s does not exist on module %s', |
|
method, |
|
module, |
|
); |
|
const result = moduleMethods[method].apply(moduleMethods, args); |
|
Systrace.endEvent(); |
|
return result; |
|
} |
|
|
|
__invokeCallback(cbID: number, args: any[]) { |
|
this._lastFlush = new Date().getTime(); |
|
this._eventLoopStartTime = this._lastFlush; |
|
|
|
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left. |
|
// eslint-disable-next-line no-bitwise |
|
const callID = cbID >>> 1; |
|
// eslint-disable-next-line no-bitwise |
|
const isSuccess = cbID & 1; |
|
const callback = isSuccess |
|
? this._successCallbacks[callID] |
|
: this._failureCallbacks[callID]; |
|
|
|
if (__DEV__) { |
|
const debug = this._debugInfo[callID]; |
|
const module = debug && this._remoteModuleTable[debug[0]]; |
|
const method = debug && this._remoteMethodTable[debug[0]][debug[1]]; |
|
if (!callback) { |
|
let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`; |
|
if (method) { |
|
errorMessage = |
|
`The callback ${method}() exists in module ${module}, ` + |
|
'but only one callback may be registered to a function in a native module.'; |
|
} |
|
invariant(callback, errorMessage); |
|
} |
|
const profileName = debug |
|
? '<callback for ' + module + '.' + method + '>' |
|
: cbID; |
|
if (callback && this.__spy) { |
|
this.__spy({type: TO_JS, module: null, method: profileName, args}); |
|
} |
|
Systrace.beginEvent( |
|
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`, |
|
); |
|
} |
|
|
|
if (!callback) { |
|
return; |
|
} |
|
|
|
this._successCallbacks[callID] = this._failureCallbacks[callID] = null; |
|
callback(...args); |
|
|
|
if (__DEV__) { |
|
Systrace.endEvent(); |
|
} |
|
} |
|
} |
|
|
|
module.exports = MessageQueue;
|
|
|