NativeModules.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. *
  7. * @format
  8. * @flow
  9. */
  10. 'use strict';
  11. const BatchedBridge = require('./BatchedBridge');
  12. const invariant = require('invariant');
  13. import type {ExtendedError} from '../Core/Devtools/parseErrorStack';
  14. export type ModuleConfig = [
  15. string /* name */,
  16. ?Object /* constants */,
  17. ?$ReadOnlyArray<string> /* functions */,
  18. ?$ReadOnlyArray<number> /* promise method IDs */,
  19. ?$ReadOnlyArray<number> /* sync method IDs */,
  20. ];
  21. export type MethodType = 'async' | 'promise' | 'sync';
  22. function genModule(
  23. config: ?ModuleConfig,
  24. moduleID: number,
  25. ): ?{
  26. name: string,
  27. module?: Object,
  28. ...
  29. } {
  30. if (!config) {
  31. return null;
  32. }
  33. const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
  34. invariant(
  35. !moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
  36. "Module name prefixes should've been stripped by the native side " +
  37. "but wasn't for " +
  38. moduleName,
  39. );
  40. if (!constants && !methods) {
  41. // Module contents will be filled in lazily later
  42. return {name: moduleName};
  43. }
  44. const module = {};
  45. methods &&
  46. methods.forEach((methodName, methodID) => {
  47. const isPromise =
  48. promiseMethods && arrayContains(promiseMethods, methodID);
  49. const isSync = syncMethods && arrayContains(syncMethods, methodID);
  50. invariant(
  51. !isPromise || !isSync,
  52. 'Cannot have a method that is both async and a sync hook',
  53. );
  54. const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
  55. module[methodName] = genMethod(moduleID, methodID, methodType);
  56. });
  57. Object.assign(module, constants);
  58. if (module.getConstants == null) {
  59. module.getConstants = () => constants || Object.freeze({});
  60. } else {
  61. console.warn(
  62. `Unable to define method 'getConstants()' on NativeModule '${moduleName}'. NativeModule '${moduleName}' already has a constant or method called 'getConstants'. Please remove it.`,
  63. );
  64. }
  65. if (__DEV__) {
  66. BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
  67. }
  68. return {name: moduleName, module};
  69. }
  70. // export this method as a global so we can call it from native
  71. global.__fbGenNativeModule = genModule;
  72. function loadModule(name: string, moduleID: number): ?Object {
  73. invariant(
  74. global.nativeRequireModuleConfig,
  75. "Can't lazily create module without nativeRequireModuleConfig",
  76. );
  77. const config = global.nativeRequireModuleConfig(name);
  78. const info = genModule(config, moduleID);
  79. return info && info.module;
  80. }
  81. function genMethod(moduleID: number, methodID: number, type: MethodType) {
  82. let fn = null;
  83. if (type === 'promise') {
  84. fn = function promiseMethodWrapper(...args: Array<any>) {
  85. // In case we reject, capture a useful stack trace here.
  86. const enqueueingFrameError: ExtendedError = new Error();
  87. return new Promise((resolve, reject) => {
  88. BatchedBridge.enqueueNativeCall(
  89. moduleID,
  90. methodID,
  91. args,
  92. data => resolve(data),
  93. errorData =>
  94. reject(updateErrorWithErrorData(errorData, enqueueingFrameError)),
  95. );
  96. });
  97. };
  98. } else {
  99. fn = function nonPromiseMethodWrapper(...args: Array<any>) {
  100. const lastArg = args.length > 0 ? args[args.length - 1] : null;
  101. const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
  102. const hasSuccessCallback = typeof lastArg === 'function';
  103. const hasErrorCallback = typeof secondLastArg === 'function';
  104. hasErrorCallback &&
  105. invariant(
  106. hasSuccessCallback,
  107. 'Cannot have a non-function arg after a function arg.',
  108. );
  109. const onSuccess = hasSuccessCallback ? lastArg : null;
  110. const onFail = hasErrorCallback ? secondLastArg : null;
  111. const callbackCount = hasSuccessCallback + hasErrorCallback;
  112. args = args.slice(0, args.length - callbackCount);
  113. if (type === 'sync') {
  114. return BatchedBridge.callNativeSyncHook(
  115. moduleID,
  116. methodID,
  117. args,
  118. onFail,
  119. onSuccess,
  120. );
  121. } else {
  122. BatchedBridge.enqueueNativeCall(
  123. moduleID,
  124. methodID,
  125. args,
  126. onFail,
  127. onSuccess,
  128. );
  129. }
  130. };
  131. }
  132. fn.type = type;
  133. return fn;
  134. }
  135. function arrayContains<T>(array: $ReadOnlyArray<T>, value: T): boolean {
  136. return array.indexOf(value) !== -1;
  137. }
  138. function updateErrorWithErrorData(
  139. errorData: {message: string, ...},
  140. error: ExtendedError,
  141. ): ExtendedError {
  142. return Object.assign(error, errorData || {});
  143. }
  144. let NativeModules: {[moduleName: string]: Object, ...} = {};
  145. if (global.nativeModuleProxy) {
  146. NativeModules = global.nativeModuleProxy;
  147. } else if (!global.nativeExtensions) {
  148. const bridgeConfig = global.__fbBatchedBridgeConfig;
  149. invariant(
  150. bridgeConfig,
  151. '__fbBatchedBridgeConfig is not set, cannot invoke native modules',
  152. );
  153. const defineLazyObjectProperty = require('../Utilities/defineLazyObjectProperty');
  154. (bridgeConfig.remoteModuleConfig || []).forEach(
  155. (config: ModuleConfig, moduleID: number) => {
  156. // Initially this config will only contain the module name when running in JSC. The actual
  157. // configuration of the module will be lazily loaded.
  158. const info = genModule(config, moduleID);
  159. if (!info) {
  160. return;
  161. }
  162. if (info.module) {
  163. NativeModules[info.name] = info.module;
  164. }
  165. // If there's no module config, define a lazy getter
  166. else {
  167. defineLazyObjectProperty(NativeModules, info.name, {
  168. get: () => loadModule(info.name, moduleID),
  169. });
  170. }
  171. },
  172. );
  173. }
  174. module.exports = NativeModules;