expo-modules-core 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/ExpoModulesCore.podspec +26 -5
  3. package/android/CMakeLists.txt +2 -1
  4. package/android/ExpoModulesCorePlugin.gradle +4 -0
  5. package/android/build.gradle +24 -31
  6. package/android/src/main/AndroidManifest.xml +1 -2
  7. package/android/src/main/cpp/ExpoModulesHostObject.cpp +3 -0
  8. package/android/src/main/cpp/JNIDeallocator.cpp +17 -0
  9. package/android/src/main/cpp/JNIDeallocator.h +25 -0
  10. package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +8 -1
  11. package/android/src/main/cpp/JSIInteropModuleRegistry.h +6 -1
  12. package/android/src/main/cpp/JavaCallback.cpp +9 -0
  13. package/android/src/main/cpp/JavaCallback.h +12 -2
  14. package/android/src/main/cpp/JavaScriptFunction.cpp +13 -0
  15. package/android/src/main/cpp/JavaScriptFunction.h +7 -1
  16. package/android/src/main/cpp/JavaScriptModuleObject.cpp +2 -1
  17. package/android/src/main/cpp/JavaScriptObject.cpp +17 -2
  18. package/android/src/main/cpp/JavaScriptObject.h +10 -3
  19. package/android/src/main/cpp/JavaScriptRuntime.cpp +5 -4
  20. package/android/src/main/cpp/JavaScriptRuntime.h +5 -3
  21. package/android/src/main/cpp/JavaScriptTypedArray.cpp +14 -0
  22. package/android/src/main/cpp/JavaScriptTypedArray.h +6 -0
  23. package/android/src/main/cpp/JavaScriptValue.cpp +32 -4
  24. package/android/src/main/cpp/JavaScriptValue.h +10 -3
  25. package/android/src/main/cpp/MethodMetadata.cpp +1 -1
  26. package/android/src/main/cpp/types/FrontendConverter.cpp +8 -4
  27. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.java +14 -0
  28. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +4 -1
  29. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +7 -3
  30. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +19 -12
  31. package/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +70 -0
  32. package/android/src/main/java/expo/modules/kotlin/devtools/ExpoRequestCdpInterceptor.kt +72 -0
  33. package/android/src/main/java/expo/modules/kotlin/devtools/OkHttpHeadersExtension.kt +18 -0
  34. package/android/src/main/java/expo/modules/kotlin/devtools/cdp/CdpNetworkTypes.kt +257 -0
  35. package/android/src/main/java/expo/modules/kotlin/jni/JNIDeallocator.kt +24 -15
  36. package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +8 -1
  37. package/android/src/main/java/expo/modules/kotlin/jni/JavaCallback.kt +0 -4
  38. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptFunction.kt +0 -4
  39. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +5 -2
  40. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptObject.kt +0 -5
  41. package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptValue.kt +0 -4
  42. package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +1 -1
  43. package/android/src/main/java/expo/modules/kotlin/types/ColorTypeConverter.kt +3 -0
  44. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +1 -2
  45. package/android-annotation/build.gradle +2 -2
  46. package/android-annotation-processor/build.gradle +2 -2
  47. package/ios/AppDelegates/ExpoAppDelegate.swift +8 -1
  48. package/ios/JSI/EXJSIInstaller.h +2 -2
  49. package/ios/JSI/EXJSIInstaller.mm +6 -6
  50. package/ios/JSI/EXJavaScriptRuntime.h +0 -6
  51. package/ios/JSI/EXJavaScriptRuntime.mm +0 -23
  52. package/ios/RCTComponentData+Privates.h +17 -0
  53. package/ios/RCTComponentData+Privates.m +15 -0
  54. package/ios/Swift/AppContext.swift +20 -11
  55. package/ios/Swift/DevTools/CdpNetworkTypes.swift +163 -0
  56. package/ios/Swift/DevTools/ExpoRequestCdpInterceptor.swift +71 -0
  57. package/ios/Swift/DevTools/ExpoRequestInterceptorProtocol.swift +183 -0
  58. package/ios/Swift/DevTools/URLRequest+httpBodyData.swift +43 -0
  59. package/ios/Swift/ExpoRuntime.swift +28 -0
  60. package/ios/Swift/Modules/CoreModule.swift +7 -0
  61. package/ios/Swift/SharedObjects/SharedRef.swift +12 -0
  62. package/ios/Swift/Views/ComponentData.swift +1 -1
  63. package/ios/Tests/CoreModuleSpec.swift +27 -0
  64. package/ios/Tests/ExpoRequestCdpInterceptorSpec.swift +165 -0
  65. package/ios/Tests/SharedRefSpec.swift +60 -0
  66. package/package.json +2 -2
@@ -2,6 +2,8 @@
2
2
 
3
3
  #pragma once
4
4
 
5
+ #include "JNIDeallocator.h"
6
+
5
7
  #include <jsi/jsi.h>
6
8
  #include <fbjni/fbjni.h>
7
9
  #include <ReactCommon/CallInvoker.h>
@@ -65,19 +67,19 @@ public:
65
67
  * @throws if the input format is unknown, or evaluation causes an error,
66
68
  * a jni::JniException<JavaScriptEvaluateException> will be thrown.
67
69
  */
68
- jni::local_ref<jni::HybridClass<JavaScriptValue>::javaobject> evaluateScript(
70
+ jni::local_ref<jni::HybridClass<JavaScriptValue, Destructible>::javaobject> evaluateScript(
69
71
  const std::string &script
70
72
  );
71
73
 
72
74
  /**
73
75
  * Returns the runtime global object for use in Kotlin.
74
76
  */
75
- jni::local_ref<jni::HybridClass<JavaScriptObject>::javaobject> global();
77
+ jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> global();
76
78
 
77
79
  /**
78
80
  * Creates a new object for use in Kotlin.
79
81
  */
80
- jni::local_ref<jni::HybridClass<JavaScriptObject>::javaobject> createObject();
82
+ jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> createObject();
81
83
 
82
84
  /**
83
85
  * Drains the JavaScript VM internal Microtask (a.k.a. event loop) queue.
@@ -1,6 +1,7 @@
1
1
  #include "JavaScriptTypedArray.h"
2
2
 
3
3
  #include "JavaScriptRuntime.h"
4
+ #include "JSIInteropModuleRegistry.h"
4
5
 
5
6
  namespace expo {
6
7
 
@@ -88,4 +89,17 @@ void JavaScriptTypedArray::writeBuffer(
88
89
  auto region = buffer->getRegion(0, size);
89
90
  memcpy(rawPointer + position, region.get(), size);
90
91
  }
92
+
93
+ jni::local_ref<JavaScriptTypedArray::javaobject> JavaScriptTypedArray::newInstance(
94
+ JSIInteropModuleRegistry *jsiInteropModuleRegistry,
95
+ std::weak_ptr<JavaScriptRuntime> runtime,
96
+ std::shared_ptr<jsi::Object> jsObject
97
+ ) {
98
+ auto object = JavaScriptTypedArray::newObjectCxxArgs(
99
+ std::move(runtime),
100
+ std::move(jsObject)
101
+ );
102
+ jsiInteropModuleRegistry->jniDeallocator->addReference(object);
103
+ return object;
104
+ }
91
105
  }
@@ -25,6 +25,12 @@ public:
25
25
 
26
26
  static void registerNatives();
27
27
 
28
+ static jni::local_ref<JavaScriptTypedArray::javaobject> newInstance(
29
+ JSIInteropModuleRegistry *jsiInteropModuleRegistry,
30
+ std::weak_ptr<JavaScriptRuntime> runtime,
31
+ std::shared_ptr<jsi::Object> jsObject
32
+ );
33
+
28
34
  JavaScriptTypedArray(
29
35
  std::weak_ptr<JavaScriptRuntime> runtime,
30
36
  std::shared_ptr<jsi::Object> jsObject
@@ -8,6 +8,7 @@
8
8
  #include "JavaScriptFunction.h"
9
9
  #include "TypedArray.h"
10
10
  #include "Exceptions.h"
11
+ #include "JSIInteropModuleRegistry.h"
11
12
 
12
13
  namespace expo {
13
14
  void JavaScriptValue::registerNatives() {
@@ -155,18 +156,27 @@ std::string JavaScriptValue::getString() {
155
156
  jni::local_ref<JavaScriptObject::javaobject> JavaScriptValue::getObject() {
156
157
  auto &jsRuntime = runtimeHolder.getJSRuntime();
157
158
  auto jsObject = std::make_shared<jsi::Object>(jsValue->getObject(jsRuntime));
158
- return JavaScriptObject::newObjectCxxArgs(runtimeHolder, jsObject);
159
+ return JavaScriptObject::newInstance(
160
+ runtimeHolder.getModuleRegistry(),
161
+ runtimeHolder,
162
+ jsObject
163
+ );
159
164
  }
160
165
 
161
166
  jni::local_ref<JavaScriptFunction::javaobject> JavaScriptValue::jniGetFunction() {
162
167
  auto &jsRuntime = runtimeHolder.getJSRuntime();
163
168
  auto jsFunction = std::make_shared<jsi::Function>(
164
169
  jsValue->getObject(jsRuntime).asFunction(jsRuntime));
165
- return JavaScriptFunction::newObjectCxxArgs(runtimeHolder, jsFunction);
170
+ return JavaScriptFunction::newInstance(
171
+ runtimeHolder.getModuleRegistry(),
172
+ runtimeHolder,
173
+ jsFunction
174
+ );
166
175
  }
167
176
 
168
177
  jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> JavaScriptValue::getArray() {
169
178
  auto &jsRuntime = runtimeHolder.getJSRuntime();
179
+ auto moduleRegistry = runtimeHolder.getModuleRegistry();
170
180
 
171
181
  auto jsArray = jsValue
172
182
  ->getObject(jsRuntime)
@@ -175,7 +185,8 @@ jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> JavaScriptValue::g
175
185
 
176
186
  auto result = jni::JArrayClass<JavaScriptValue::javaobject>::newArray(size);
177
187
  for (size_t i = 0; i < size; i++) {
178
- auto element = JavaScriptValue::newObjectCxxArgs(
188
+ auto element = JavaScriptValue::newInstance(
189
+ moduleRegistry,
179
190
  runtimeHolder,
180
191
  std::make_shared<jsi::Value>(jsArray.getValueAtIndex(jsRuntime, i))
181
192
  );
@@ -198,6 +209,23 @@ jni::local_ref<jstring> JavaScriptValue::jniGetString() {
198
209
  jni::local_ref<JavaScriptTypedArray::javaobject> JavaScriptValue::getTypedArray() {
199
210
  auto &jsRuntime = runtimeHolder.getJSRuntime();
200
211
  auto jsObject = std::make_shared<jsi::Object>(jsValue->getObject(jsRuntime));
201
- return JavaScriptTypedArray::newObjectCxxArgs(runtimeHolder, jsObject);
212
+ return JavaScriptTypedArray::newInstance(
213
+ runtimeHolder.getModuleRegistry(),
214
+ runtimeHolder,
215
+ jsObject
216
+ );
217
+ }
218
+
219
+ jni::local_ref<JavaScriptValue::javaobject> JavaScriptValue::newInstance(
220
+ JSIInteropModuleRegistry *jsiInteropModuleRegistry,
221
+ std::weak_ptr<JavaScriptRuntime> runtime,
222
+ std::shared_ptr<jsi::Value> jsValue
223
+ ) {
224
+ auto value = JavaScriptValue::newObjectCxxArgs(
225
+ std::move(runtime),
226
+ std::move(jsValue)
227
+ );
228
+ jsiInteropModuleRegistry->jniDeallocator->addReference(value);
229
+ return value;
202
230
  }
203
231
  } // namespace expo
@@ -5,6 +5,7 @@
5
5
  #include "JSIObjectWrapper.h"
6
6
  #include "WeakRuntimeHolder.h"
7
7
  #include "JavaScriptTypedArray.h"
8
+ #include "JNIDeallocator.h"
8
9
 
9
10
  #include <fbjni/fbjni.h>
10
11
  #include <jsi/jsi.h>
@@ -26,7 +27,7 @@ class JavaScriptFunction;
26
27
  /**
27
28
  * Represents any JavaScript value. Its purpose is to expose the `jsi::Value` API back to Kotlin.
28
29
  */
29
- class JavaScriptValue : public jni::HybridClass<JavaScriptValue>, JSIValueWrapper {
30
+ class JavaScriptValue : public jni::HybridClass<JavaScriptValue, Destructible>, JSIValueWrapper {
30
31
  public:
31
32
  static auto constexpr
32
33
  kJavaDescriptor = "Lexpo/modules/kotlin/jni/JavaScriptValue;";
@@ -34,6 +35,12 @@ public:
34
35
 
35
36
  static void registerNatives();
36
37
 
38
+ static jni::local_ref<JavaScriptValue::javaobject> newInstance(
39
+ JSIInteropModuleRegistry *jsiInteropModuleRegistry,
40
+ std::weak_ptr<JavaScriptRuntime> runtime,
41
+ std::shared_ptr<jsi::Value> jsValue
42
+ );
43
+
37
44
  JavaScriptValue(
38
45
  std::weak_ptr<JavaScriptRuntime> runtime,
39
46
  std::shared_ptr<jsi::Value> jsValue
@@ -74,13 +81,13 @@ public:
74
81
 
75
82
  std::string getString();
76
83
 
77
- jni::local_ref<jni::HybridClass<JavaScriptObject>::javaobject> getObject();
84
+ jni::local_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> getObject();
78
85
 
79
86
  jni::local_ref<jni::JArrayClass<JavaScriptValue::javaobject>> getArray();
80
87
 
81
88
  jni::local_ref<JavaScriptTypedArray::javaobject> getTypedArray();
82
89
 
83
- jni::local_ref<jni::HybridClass<JavaScriptFunction>::javaobject> jniGetFunction();
90
+ jni::local_ref<jni::HybridClass<JavaScriptFunction, Destructible>::javaobject> jniGetFunction();
84
91
 
85
92
  private:
86
93
  friend HybridBase;
@@ -102,7 +102,7 @@ jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction(
102
102
  wrapperWasCalled = true;
103
103
  };
104
104
 
105
- return JavaCallback::newObjectCxxArgs(std::move(fn));
105
+ return JavaCallback::newInstance(moduleRegistry, std::move(fn));
106
106
  }
107
107
 
108
108
  jobjectArray MethodMetadata::convertJSIArgsToJNI(
@@ -159,7 +159,8 @@ jobject TypedArrayFrontendConverter::convert(
159
159
  JSIInteropModuleRegistry *moduleRegistry,
160
160
  const jsi::Value &value
161
161
  ) const {
162
- return JavaScriptTypedArray::newObjectCxxArgs(
162
+ return JavaScriptTypedArray::newInstance(
163
+ moduleRegistry,
163
164
  moduleRegistry->runtimeHolder->weak_from_this(),
164
165
  std::make_shared<jsi::Object>(value.getObject(rt))
165
166
  ).release();
@@ -178,7 +179,8 @@ jobject JavaScriptValueFrontendConverter::convert(
178
179
  JSIInteropModuleRegistry *moduleRegistry,
179
180
  const jsi::Value &value
180
181
  ) const {
181
- return JavaScriptValue::newObjectCxxArgs(
182
+ return JavaScriptValue::newInstance(
183
+ moduleRegistry,
182
184
  moduleRegistry->runtimeHolder->weak_from_this(),
183
185
  // TODO(@lukmccall): make sure that copy here is necessary
184
186
  std::make_shared<jsi::Value>(jsi::Value(rt, value))
@@ -195,7 +197,8 @@ jobject JavaScriptObjectFrontendConverter::convert(
195
197
  JSIInteropModuleRegistry *moduleRegistry,
196
198
  const jsi::Value &value
197
199
  ) const {
198
- return JavaScriptObject::newObjectCxxArgs(
200
+ return JavaScriptObject::newInstance(
201
+ moduleRegistry,
199
202
  moduleRegistry->runtimeHolder->weak_from_this(),
200
203
  std::make_shared<jsi::Object>(value.getObject(rt))
201
204
  ).release();
@@ -214,7 +217,8 @@ jobject JavaScriptFunctionFrontendConverter::convert(
214
217
  JSIInteropModuleRegistry *moduleRegistry,
215
218
  const jsi::Value &value
216
219
  ) const {
217
- return JavaScriptFunction::newObjectCxxArgs(
220
+ return JavaScriptFunction::newInstance(
221
+ moduleRegistry,
218
222
  moduleRegistry->runtimeHolder->weak_from_this(),
219
223
  std::make_shared<jsi::Function>(value.getObject(rt).asFunction(rt))
220
224
  ).release();
@@ -6,6 +6,7 @@ import android.view.ViewGroup;
6
6
 
7
7
  import com.facebook.react.ReactActivity;
8
8
  import com.facebook.react.ReactActivityDelegate;
9
+ import com.facebook.react.ReactNativeHost;
9
10
  import com.facebook.react.ReactRootView;
10
11
 
11
12
  import androidx.annotation.Nullable;
@@ -54,4 +55,17 @@ public interface ReactActivityHandler {
54
55
  default ReactActivityDelegate onDidCreateReactActivityDelegate(ReactActivity activity, ReactActivityDelegate delegate) {
55
56
  return null;
56
57
  }
58
+
59
+ /**
60
+ * For modules to delay the call for react-native `loadApp`.
61
+ * This gives modules a chance to do some early and heavy initialization in background thread and avoid ANR.
62
+ * Right now it is for expo-updates only.
63
+ */
64
+ @Nullable
65
+ default DelayLoadAppHandler getDelayLoadAppHandler(ReactActivity activity, ReactNativeHost reactNativeHost) {
66
+ return null;
67
+ }
68
+ interface DelayLoadAppHandler {
69
+ void whenReady(Runnable runnable);
70
+ }
57
71
  }
@@ -91,6 +91,8 @@ class AppContext(
91
91
  CoroutineName("expo.modules.MainQueue")
92
92
  )
93
93
 
94
+ val jniDeallocator: JNIDeallocator = JNIDeallocator()
95
+
94
96
  internal var legacyModulesProxyHolder: WeakReference<NativeModulesProxy>? = null
95
97
 
96
98
  private val activityResultsManager = ActivityResultsManager(this)
@@ -130,6 +132,7 @@ class AppContext(
130
132
  ?.let {
131
133
  jsiInterop.installJSI(
132
134
  it,
135
+ jniDeallocator,
133
136
  jsContextProvider.jsCallInvokerHolder,
134
137
  catalystInstance.nativeCallInvokerHolder as CallInvokerHolderImpl
135
138
  )
@@ -275,7 +278,7 @@ class AppContext(
275
278
  modulesQueue.cancel(ContextDestroyedException())
276
279
  mainQueue.cancel(ContextDestroyedException())
277
280
  backgroundCoroutineScope.cancel(ContextDestroyedException())
278
- JNIDeallocator.deallocate()
281
+ jniDeallocator.deallocate()
279
282
  logger.info("✅ AppContext was destroyed")
280
283
  }
281
284
 
@@ -24,13 +24,16 @@ class ModuleHolder(val module: Module) {
24
24
  */
25
25
  val jsObject by lazy {
26
26
  val appContext = module.appContext
27
+ val jniDeallocator = appContext.jniDeallocator
27
28
 
28
- JavaScriptModuleObject(name).apply {
29
+ JavaScriptModuleObject(jniDeallocator, name).apply {
29
30
  initUsingObjectDefinition(appContext, definition.objectDefinition)
30
31
 
31
32
  val viewFunctions = definition.viewManagerDefinition?.asyncFunctions
32
33
  if (viewFunctions?.isNotEmpty() == true) {
33
- val viewPrototype = JavaScriptModuleObject("${name}_${definition.viewManagerDefinition?.viewType?.name}")
34
+ val viewPrototype = JavaScriptModuleObject(jniDeallocator, "${name}_${definition.viewManagerDefinition?.viewType?.name}")
35
+ appContext.jniDeallocator.addReference(viewPrototype)
36
+
34
37
  viewFunctions.forEach { function ->
35
38
  function.attachToJSObject(appContext, viewPrototype)
36
39
  }
@@ -39,8 +42,9 @@ class ModuleHolder(val module: Module) {
39
42
  }
40
43
 
41
44
  definition.classData.forEach { clazz ->
42
- val clazzModuleObject = JavaScriptModuleObject(clazz.name)
45
+ val clazzModuleObject = JavaScriptModuleObject(jniDeallocator, clazz.name)
43
46
  .initUsingObjectDefinition(module.appContext, clazz.objectDefinition)
47
+ appContext.jniDeallocator.addReference(clazzModuleObject)
44
48
 
45
49
  val constructor = clazz.constructor
46
50
  registerClass(
@@ -17,8 +17,10 @@ class ModuleRegistry(
17
17
  internal val registry = mutableMapOf<String, ModuleHolder>()
18
18
 
19
19
  fun register(module: Module) {
20
- val holder = ModuleHolder(module)
21
20
  module._appContext = requireNotNull(appContext.get()) { "Cannot create a module for invalid app context." }
21
+
22
+ val holder = ModuleHolder(module)
23
+
22
24
  module.coroutineScopeDelegate = lazy {
23
25
  CoroutineScope(
24
26
  Dispatchers.Default +
@@ -26,19 +28,24 @@ class ModuleRegistry(
26
28
  CoroutineName(holder.definition.name)
27
29
  )
28
30
  }
29
- holder.post(EventName.MODULE_CREATE)
30
- holder.registerContracts()
31
- // The initial invocation of `declaredMemberProperties` appears to be slow,
32
- // as Kotlin must deserialize metadata internally.
33
- // This is a known issue that may be resolved by the new K2 compiler in the future.
34
- // However, until then, we must find a way to address this problem.
35
- // Therefore, we have decided to dispatch a lambda
36
- // that invokes `declaredMemberProperties` during module creation.
37
- holder.viewClass()?.let { viewType ->
38
- appContext.get()?.backgroundCoroutineScope?.launch {
39
- viewType.declaredMemberProperties
31
+
32
+ holder.apply {
33
+ post(EventName.MODULE_CREATE)
34
+ registerContracts()
35
+
36
+ // The initial invocation of `declaredMemberProperties` appears to be slow,
37
+ // as Kotlin must deserialize metadata internally.
38
+ // This is a known issue that may be resolved by the new K2 compiler in the future.
39
+ // However, until then, we must find a way to address this problem.
40
+ // Therefore, we have decided to dispatch a lambda
41
+ // that invokes `declaredMemberProperties` during module creation.
42
+ viewClass()?.let { viewType ->
43
+ appContext.get()?.backgroundCoroutineScope?.launch {
44
+ viewType.declaredMemberProperties
45
+ }
40
46
  }
41
47
  }
48
+
42
49
  registry[holder.name] = holder
43
50
  }
44
51
 
@@ -0,0 +1,70 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.kotlin.devtools
4
+
5
+ import okhttp3.Interceptor
6
+ import okhttp3.Request
7
+ import okhttp3.Response
8
+
9
+ // Currently keeps the delegate fixed for ExpoRequestCdpInterceptor and be thread-safe
10
+ internal val delegate: ExpoNetworkInspectOkHttpInterceptorsDelegate = ExpoRequestCdpInterceptor
11
+
12
+ /**
13
+ * The OkHttp network interceptor to log requests and the CDP events to the delegate
14
+ */
15
+ @Suppress("unused")
16
+ class ExpoNetworkInspectOkHttpNetworkInterceptor : Interceptor {
17
+ override fun intercept(chain: Interceptor.Chain): Response {
18
+ val request = chain.request()
19
+ val redirectResponse = request.tag(RedirectResponse::class.java)
20
+ val requestId = redirectResponse?.requestId ?: request.hashCode().toString()
21
+ delegate.willSendRequest(requestId, request, redirectResponse?.priorResponse)
22
+
23
+ val response = chain.proceed(request)
24
+
25
+ if (response.isRedirect) {
26
+ response.request.tag(RedirectResponse::class.java)?.let {
27
+ it.requestId = requestId
28
+ it.priorResponse = response
29
+ }
30
+ } else {
31
+ delegate.didReceiveResponse(requestId, request, response)
32
+ }
33
+ return response
34
+ }
35
+
36
+ companion object {
37
+ const val MAX_BODY_SIZE = 1048576L
38
+ }
39
+ }
40
+
41
+ /**
42
+ * The OkHttp app interceptor to add custom tag for [RedirectResponse]
43
+ */
44
+ @Suppress("unused")
45
+ class ExpoNetworkInspectOkHttpAppInterceptor : Interceptor {
46
+ override fun intercept(chain: Interceptor.Chain): Response {
47
+ return chain.proceed(
48
+ chain.request().newBuilder()
49
+ .tag(RedirectResponse::class.java, RedirectResponse())
50
+ .build()
51
+ )
52
+ }
53
+ }
54
+
55
+ /**
56
+ * The delegate to dispatch network request events
57
+ */
58
+ internal interface ExpoNetworkInspectOkHttpInterceptorsDelegate {
59
+ fun willSendRequest(requestId: String, request: Request, redirectResponse: Response?)
60
+
61
+ fun didReceiveResponse(requestId: String, request: Request, response: Response)
62
+ }
63
+
64
+ /**
65
+ * Custom property for redirect requests
66
+ */
67
+ internal class RedirectResponse {
68
+ var requestId: String? = null
69
+ var priorResponse: Response? = null
70
+ }
@@ -0,0 +1,72 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.kotlin.devtools
4
+
5
+ import expo.modules.kotlin.devtools.cdp.Event
6
+ import expo.modules.kotlin.devtools.cdp.ExpoReceivedResponseBodyParams
7
+ import expo.modules.kotlin.devtools.cdp.LoadingFinishedParams
8
+ import expo.modules.kotlin.devtools.cdp.RequestWillBeSentExtraInfoParams
9
+ import expo.modules.kotlin.devtools.cdp.RequestWillBeSentParams
10
+ import expo.modules.kotlin.devtools.cdp.ResponseReceivedParams
11
+ import kotlinx.coroutines.CoroutineScope
12
+ import kotlinx.coroutines.Dispatchers
13
+ import kotlinx.coroutines.launch
14
+ import okhttp3.Request
15
+ import okhttp3.Response
16
+ import java.math.BigDecimal
17
+ import java.math.RoundingMode
18
+
19
+ /**
20
+ * The `ExpoRequestInterceptorProtocolDelegate` implementation to
21
+ * dispatch CDP (Chrome DevTools Protocol: https://chromedevtools.github.io/devtools-protocol/) events.
22
+ */
23
+ object ExpoRequestCdpInterceptor : ExpoNetworkInspectOkHttpInterceptorsDelegate {
24
+ private var delegate: Delegate? = null
25
+ internal var coroutineScope = CoroutineScope(Dispatchers.Default)
26
+
27
+ fun setDelegate(delegate: Delegate?) {
28
+ coroutineScope.launch {
29
+ this@ExpoRequestCdpInterceptor.delegate = delegate
30
+ }
31
+ }
32
+
33
+ private fun dispatchEvent(event: Event) {
34
+ coroutineScope.launch {
35
+ this@ExpoRequestCdpInterceptor.delegate?.dispatch(event.toJson())
36
+ }
37
+ }
38
+
39
+ //region ExpoNetworkInspectOkHttpInterceptorsDelegate implementations
40
+
41
+ override fun willSendRequest(requestId: String, request: Request, redirectResponse: Response?) {
42
+ val now = BigDecimal(System.currentTimeMillis() / 1000.0).setScale(3, RoundingMode.CEILING)
43
+
44
+ val params = RequestWillBeSentParams(now, requestId, request, redirectResponse)
45
+ dispatchEvent(Event("Network.requestWillBeSent", params))
46
+
47
+ val params2 = RequestWillBeSentExtraInfoParams(now, requestId, request)
48
+ dispatchEvent(Event("Network.requestWillBeSentExtraInfo", params2))
49
+ }
50
+
51
+ override fun didReceiveResponse(requestId: String, request: Request, response: Response) {
52
+ val now = BigDecimal(System.currentTimeMillis() / 1000.0).setScale(3, RoundingMode.CEILING)
53
+
54
+ val params = ResponseReceivedParams(now, requestId, request, response)
55
+ dispatchEvent(Event("Network.responseReceived", params))
56
+
57
+ val params2 = LoadingFinishedParams(now, requestId, request, response)
58
+ dispatchEvent(Event("Network.loadingFinished", params2))
59
+
60
+ val contentLength = response.body?.contentLength() ?: 0
61
+ if (contentLength >= 0 && contentLength <= ExpoNetworkInspectOkHttpNetworkInterceptor.MAX_BODY_SIZE) {
62
+ val params3 = ExpoReceivedResponseBodyParams(now, requestId, request, response)
63
+ dispatchEvent(Event("Expo(Network.receivedResponseBody)", params3))
64
+ }
65
+ }
66
+
67
+ //endregion ExpoNetworkInspectOkHttpInterceptorsDelegate implementations
68
+
69
+ interface Delegate {
70
+ fun dispatch(event: String)
71
+ }
72
+ }
@@ -0,0 +1,18 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.kotlin.devtools
4
+
5
+ import androidx.collection.ArrayMap
6
+ import okhttp3.Headers
7
+
8
+ /**
9
+ * OkHttp `Headers` extension method to generate a simple key-value map
10
+ * which only exposing single value for a key.
11
+ */
12
+ fun Headers.toSingleMap(): Map<String, String> {
13
+ val result = ArrayMap<String, String>()
14
+ for (key in names()) {
15
+ result[key] = get(key)
16
+ }
17
+ return result
18
+ }