123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @format
- * @flow
- */
- 'use strict';
- const BatchedBridge = require('./BatchedBridge');
- const invariant = require('invariant');
- import type {ExtendedError} from '../Core/Devtools/parseErrorStack';
- export type ModuleConfig = [
- string /* name */,
- ?Object /* constants */,
- ?$ReadOnlyArray<string> /* functions */,
- ?$ReadOnlyArray<number> /* promise method IDs */,
- ?$ReadOnlyArray<number> /* sync method IDs */,
- ];
- export type MethodType = 'async' | 'promise' | 'sync';
- function genModule(
- config: ?ModuleConfig,
- moduleID: number,
- ): ?{
- name: string,
- module?: Object,
- ...
- } {
- if (!config) {
- return null;
- }
- const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
- invariant(
- !moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
- "Module name prefixes should've been stripped by the native side " +
- "but wasn't for " +
- moduleName,
- );
- if (!constants && !methods) {
- // Module contents will be filled in lazily later
- return {name: moduleName};
- }
- const module = {};
- methods &&
- methods.forEach((methodName, methodID) => {
- const isPromise =
- promiseMethods && arrayContains(promiseMethods, methodID);
- const isSync = syncMethods && arrayContains(syncMethods, methodID);
- invariant(
- !isPromise || !isSync,
- 'Cannot have a method that is both async and a sync hook',
- );
- const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
- module[methodName] = genMethod(moduleID, methodID, methodType);
- });
- Object.assign(module, constants);
- if (module.getConstants == null) {
- module.getConstants = () => constants || Object.freeze({});
- } else {
- console.warn(
- `Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
- );
- }
- if (__DEV__) {
- BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
- }
- return {name: moduleName, module};
- }
- // export this method as a global so we can call it from native
- global.__fbGenNativeModule = genModule;
- function loadModule(name: string, moduleID: number): ?Object {
- invariant(
- global.nativeRequireModuleConfig,
- "Can't lazily create module without nativeRequireModuleConfig",
- );
- const config = global.nativeRequireModuleConfig(name);
- const info = genModule(config, moduleID);
- return info && info.module;
- }
- function genMethod(moduleID: number, methodID: number, type: MethodType) {
- let fn = null;
- if (type === 'promise') {
- fn = function promiseMethodWrapper(...args: Array<any>) {
- // In case we reject, capture a useful stack trace here.
- const enqueueingFrameError: ExtendedError = new Error();
- return new Promise((resolve, reject) => {
- BatchedBridge.enqueueNativeCall(
- moduleID,
- methodID,
- args,
- data => resolve(data),
- errorData =>
- reject(updateErrorWithErrorData(errorData, enqueueingFrameError)),
- );
- });
- };
- } else {
- fn = function nonPromiseMethodWrapper(...args: Array<any>) {
- const lastArg = args.length > 0 ? args[args.length - 1] : null;
- const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
- const hasSuccessCallback = typeof lastArg === 'function';
- const hasErrorCallback = typeof secondLastArg === 'function';
- hasErrorCallback &&
- invariant(
- hasSuccessCallback,
- 'Cannot have a non-function arg after a function arg.',
- );
- const onSuccess = hasSuccessCallback ? lastArg : null;
- const onFail = hasErrorCallback ? secondLastArg : null;
- const callbackCount = hasSuccessCallback + hasErrorCallback;
- args = args.slice(0, args.length - callbackCount);
- if (type === 'sync') {
- return BatchedBridge.callNativeSyncHook(
- moduleID,
- methodID,
- args,
- onFail,
- onSuccess,
- );
- } else {
- BatchedBridge.enqueueNativeCall(
- moduleID,
- methodID,
- args,
- onFail,
- onSuccess,
- );
- }
- };
- }
- fn.type = type;
- return fn;
- }
- function arrayContains<T>(array: $ReadOnlyArray<T>, value: T): boolean {
- return array.indexOf(value) !== -1;
- }
- function updateErrorWithErrorData(
- errorData: {message: string, ...},
- error: ExtendedError,
- ): ExtendedError {
- return Object.assign(error, errorData || {});
- }
- let NativeModules: {[moduleName: string]: Object, ...} = {};
- if (global.nativeModuleProxy) {
- NativeModules = global.nativeModuleProxy;
- } else if (!global.nativeExtensions) {
- const bridgeConfig = global.__fbBatchedBridgeConfig;
- invariant(
- bridgeConfig,
- '__fbBatchedBridgeConfig is not set, cannot invoke native modules',
- );
- const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
- (bridgeConfig.remoteModuleConfig || []).forEach(
- (config: ModuleConfig, moduleID: number) => {
- // Initially this config will only contain the module name when running in JSC. The actual
- // configuration of the module will be lazily loaded.
- const info = genModule(config, moduleID);
- if (!info) {
- return;
- }
- if (info.module) {
- NativeModules[info.name] = info.module;
- }
- // If there's no module config, define a lazy getter
- else {
- defineLazyObjectProperty(NativeModules, info.name, {
- get: () => loadModule(info.name, moduleID),
- });
- }
- },
- );
- }
- module.exports = NativeModules;
|