expo-modules-core 1.12.3 → 1.12.5

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.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,18 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 1.12.5 — 2024-05-01
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 1.12.4 — 2024-04-29
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [Android] Fixed gziped payload does not correctly shown in network inspector. ([#28486](https://github.com/expo/expo/pull/28486) by [@kudo](https://github.com/kudo))
22
+ - [Android] Fixed crashes during the deallocation of shared objects. ([#28491](https://github.com/expo/expo/pull/28491) by [@lukmccall](https://github.com/lukmccall))
23
+ - [iOS] Fix accessing the view registry to avoid infinite loop crash. ([#28531](https://github.com/expo/expo/pull/28531) by [@tsapeta](https://github.com/tsapeta))
24
+
13
25
  ## 1.12.3 — 2024-04-26
14
26
 
15
27
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '1.12.3'
4
+ version = '1.12.5'
5
5
 
6
6
  def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
7
  apply from: expoModulesCorePlugin
@@ -63,7 +63,7 @@ android {
63
63
  defaultConfig {
64
64
  consumerProguardFiles 'proguard-rules.pro'
65
65
  versionCode 1
66
- versionName "1.12.3"
66
+ versionName "1.12.5"
67
67
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
68
68
 
69
69
  testInstrumentationRunner "expo.modules.TestRunner"
@@ -29,6 +29,42 @@ namespace expo {
29
29
 
30
30
  #endif
31
31
 
32
+ /*
33
+ * A wrapper for a global reference that can be deallocated on any thread.
34
+ * It should be used with smart pointer. That structure can't be copied or moved.
35
+ */
36
+ template <typename T>
37
+ class ThreadSafeJNIGlobalRef {
38
+ public:
39
+ ThreadSafeJNIGlobalRef(jobject globalRef) : globalRef(globalRef) {}
40
+ ThreadSafeJNIGlobalRef(const ThreadSafeJNIGlobalRef &other) = delete;
41
+ ThreadSafeJNIGlobalRef(ThreadSafeJNIGlobalRef &&other) = delete;
42
+ ThreadSafeJNIGlobalRef &operator=(const ThreadSafeJNIGlobalRef &other) = delete;
43
+ ThreadSafeJNIGlobalRef &operator=(ThreadSafeJNIGlobalRef &&other) = delete;
44
+
45
+ void use(std::function<void(jni::alias_ref<T> globalRef)> &&action) {
46
+ if (globalRef == nullptr) {
47
+ throw std::runtime_error("ThreadSafeJNIGlobalRef: globalRef is null");
48
+ }
49
+
50
+ jni::ThreadScope::WithClassLoader([this, action = std::move(action)]() {
51
+ jni::alias_ref<jobject> aliasRef = jni::wrap_alias(globalRef);
52
+ jni::alias_ref<T> jsiContextRef = jni::static_ref_cast<T>(aliasRef);
53
+ action(jsiContextRef);
54
+ });
55
+ }
56
+
57
+ ~ThreadSafeJNIGlobalRef() {
58
+ if (globalRef != nullptr) {
59
+ jni::ThreadScope::WithClassLoader([this] {
60
+ jni::Environment::current()->DeleteGlobalRef(this->globalRef);
61
+ });
62
+ }
63
+ }
64
+
65
+ jobject globalRef;
66
+ };
67
+
32
68
  jni::local_ref<JSIContext::jhybriddata>
33
69
  JSIContext::initHybrid(jni::alias_ref<jhybridobject> jThis) {
34
70
  return makeCxxInstance(jThis);
@@ -137,13 +173,17 @@ void JSIContext::prepareRuntime() {
137
173
 
138
174
  EventEmitter::installClass(runtime);
139
175
 
176
+ auto threadSafeRef = std::make_shared<ThreadSafeJNIGlobalRef<JSIContext::javaobject>>(
177
+ jni::Environment::current()->NewGlobalRef(javaPart_.get())
178
+ );
179
+
140
180
  SharedObject::installBaseClass(
141
181
  runtime,
142
182
  // We can't predict the order of deallocation of the JSIContext and the SharedObject.
143
183
  // So we need to pass a new ref to retain the JSIContext to make sure it's not deallocated before the SharedObject.
144
- [javaObject = javaPart_](const SharedObject::ObjectId objectId) {
145
- jni::ThreadScope::WithClassLoader([objectId = objectId, javaObject = std::move(javaObject)] {
146
- JSIContext::deleteSharedObject(javaObject, objectId);
184
+ [threadSafeRef = std::move(threadSafeRef)](const SharedObject::ObjectId objectId) {
185
+ threadSafeRef->use([objectId](jni::alias_ref<JSIContext::javaobject> globalRef) {
186
+ JSIContext::deleteSharedObject(globalRef, objectId);
147
187
  });
148
188
  }
149
189
  );
@@ -169,7 +209,8 @@ void JSIContext::prepareRuntime() {
169
209
  jni::local_ref<JavaScriptModuleObject::javaobject>
170
210
  JSIContext::callGetJavaScriptModuleObjectMethod(const std::string &moduleName) const {
171
211
  if (javaPart_ == nullptr) {
172
- throw std::runtime_error("getJavaScriptModuleObject: JSIContext was prepared to be deallocated.");
212
+ throw std::runtime_error(
213
+ "getJavaScriptModuleObject: JSIContext was prepared to be deallocated.");
173
214
  }
174
215
 
175
216
  const static auto method = expo::JSIContext::javaClassLocal()
@@ -270,7 +311,7 @@ void JSIContext::registerSharedObject(
270
311
  }
271
312
 
272
313
  void JSIContext::deleteSharedObject(
273
- jni::global_ref<JSIContext::javaobject> javaObject,
314
+ jni::alias_ref<JSIContext::javaobject> javaObject,
274
315
  int objectId
275
316
  ) {
276
317
  if (javaObject == nullptr) {
@@ -324,13 +365,17 @@ void JSIContext::jniSetNativeStateForSharedObject(
324
365
  int id,
325
366
  jni::alias_ref<JavaScriptObject::javaobject> jsObject
326
367
  ) {
368
+ auto threadSafeRef = std::make_shared<ThreadSafeJNIGlobalRef<JSIContext::javaobject>>(
369
+ jni::Environment::current()->NewGlobalRef(javaPart_.get())
370
+ );
371
+
327
372
  auto nativeState = std::make_shared<expo::SharedObject::NativeState>(
328
373
  id,
329
374
  // We can't predict the order of deallocation of the JSIContext and the SharedObject.
330
375
  // So we need to pass a new ref to retain the JSIContext to make sure it's not deallocated before the SharedObject.
331
- [javaObject = javaPart_](const SharedObject::ObjectId objectId) {
332
- jni::ThreadScope::WithClassLoader([objectId = objectId, javaObject = std::move(javaObject)] {
333
- JSIContext::deleteSharedObject(javaObject, objectId);
376
+ [threadSafeRef = std::move(threadSafeRef)](const SharedObject::ObjectId objectId) {
377
+ threadSafeRef->use([objectId](jni::alias_ref<JSIContext::javaobject> globalRef) {
378
+ JSIContext::deleteSharedObject(globalRef, objectId);
334
379
  });
335
380
  }
336
381
  );
@@ -123,7 +123,7 @@ public:
123
123
  );
124
124
 
125
125
  static void deleteSharedObject(
126
- jni::global_ref<JSIContext::javaobject> javaObject,
126
+ jni::alias_ref<JSIContext::javaobject> javaObject,
127
127
  int objectId
128
128
  );
129
129
 
@@ -6,6 +6,11 @@ import android.util.Log
6
6
  import okhttp3.Interceptor
7
7
  import okhttp3.Request
8
8
  import okhttp3.Response
9
+ import okhttp3.ResponseBody
10
+ import okhttp3.ResponseBody.Companion.asResponseBody
11
+ import okio.GzipSource
12
+ import okio.buffer
13
+ import java.io.IOException
9
14
 
10
15
  private const val TAG = "ExpoNetworkInspector"
11
16
 
@@ -31,7 +36,9 @@ class ExpoNetworkInspectOkHttpNetworkInterceptor : Interceptor {
31
36
  it.priorResponse = response
32
37
  }
33
38
  } else {
34
- delegate.didReceiveResponse(requestId, request, response)
39
+ val body = peekResponseBody(response)
40
+ delegate.didReceiveResponse(requestId, request, response, body)
41
+ body?.close()
35
42
  }
36
43
  } catch (e: Exception) {
37
44
  Log.e(TAG, "Failed to send network request CDP event", e)
@@ -62,9 +69,18 @@ class ExpoNetworkInspectOkHttpAppInterceptor : Interceptor {
62
69
  * The delegate to dispatch network request events
63
70
  */
64
71
  internal interface ExpoNetworkInspectOkHttpInterceptorsDelegate {
65
- fun willSendRequest(requestId: String, request: Request, redirectResponse: Response?)
72
+ fun willSendRequest(
73
+ requestId: String,
74
+ request: Request,
75
+ redirectResponse: Response?
76
+ )
66
77
 
67
- fun didReceiveResponse(requestId: String, request: Request, response: Response)
78
+ fun didReceiveResponse(
79
+ requestId: String,
80
+ request: Request,
81
+ response: Response,
82
+ body: ResponseBody?
83
+ )
68
84
  }
69
85
 
70
86
  /**
@@ -74,3 +90,36 @@ internal class RedirectResponse {
74
90
  var requestId: String? = null
75
91
  var priorResponse: Response? = null
76
92
  }
93
+
94
+ /**
95
+ * Peek response body that could send to CDP delegate.
96
+ * Also uncompress gzip payload if necessary since OkHttp [Interceptor] does not uncompress payload for you.
97
+ * @return null if the response body exceeds [byteCount]
98
+ */
99
+ internal fun peekResponseBody(
100
+ response: Response,
101
+ byteCount: Long = ExpoNetworkInspectOkHttpNetworkInterceptor.MAX_BODY_SIZE
102
+ ): ResponseBody? {
103
+ val body = response.body ?: return null
104
+ val peeked = body.source().peek()
105
+ try {
106
+ if (peeked.request(byteCount + 1)) {
107
+ // When the request() returns true,
108
+ // it means the source have more available bytes then [byteCount].
109
+ return null
110
+ }
111
+ } catch (_: IOException) {}
112
+
113
+ val encoding = response.header("Content-Encoding")
114
+ val source = when {
115
+ encoding.equals("gzip", ignoreCase = true) ->
116
+ GzipSource(peeked).buffer().apply {
117
+ request(byteCount)
118
+ }
119
+ else -> peeked
120
+ }
121
+
122
+ val buffer = okio.Buffer()
123
+ buffer.write(source, minOf(byteCount, source.buffer.size))
124
+ return buffer.asResponseBody(body.contentType(), buffer.size)
125
+ }
@@ -13,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
13
13
  import kotlinx.coroutines.launch
14
14
  import okhttp3.Request
15
15
  import okhttp3.Response
16
+ import okhttp3.ResponseBody
16
17
  import java.math.BigDecimal
17
18
  import java.math.RoundingMode
18
19
 
@@ -38,7 +39,11 @@ object ExpoRequestCdpInterceptor : ExpoNetworkInspectOkHttpInterceptorsDelegate
38
39
 
39
40
  //region ExpoNetworkInspectOkHttpInterceptorsDelegate implementations
40
41
 
41
- override fun willSendRequest(requestId: String, request: Request, redirectResponse: Response?) {
42
+ override fun willSendRequest(
43
+ requestId: String,
44
+ request: Request,
45
+ redirectResponse: Response?
46
+ ) {
42
47
  val now = BigDecimal(System.currentTimeMillis() / 1000.0).setScale(3, RoundingMode.CEILING)
43
48
 
44
49
  val params = RequestWillBeSentParams(now, requestId, request, redirectResponse)
@@ -48,14 +53,19 @@ object ExpoRequestCdpInterceptor : ExpoNetworkInspectOkHttpInterceptorsDelegate
48
53
  dispatchEvent(Event("Network.requestWillBeSentExtraInfo", params2))
49
54
  }
50
55
 
51
- override fun didReceiveResponse(requestId: String, request: Request, response: Response) {
56
+ override fun didReceiveResponse(
57
+ requestId: String,
58
+ request: Request,
59
+ response: Response,
60
+ body: ResponseBody?
61
+ ) {
52
62
  val now = BigDecimal(System.currentTimeMillis() / 1000.0).setScale(3, RoundingMode.CEILING)
53
63
 
54
64
  val params = ResponseReceivedParams(now, requestId, request, response)
55
65
  dispatchEvent(Event("Network.responseReceived", params))
56
66
 
57
- if (response.peekBody(ExpoNetworkInspectOkHttpNetworkInterceptor.MAX_BODY_SIZE + 1).contentLength() <= ExpoNetworkInspectOkHttpNetworkInterceptor.MAX_BODY_SIZE) {
58
- val params2 = ExpoReceivedResponseBodyParams(now, requestId, request, response)
67
+ if (body != null) {
68
+ val params2 = ExpoReceivedResponseBodyParams(now, requestId, request, response, body)
59
69
  dispatchEvent(Event("Expo(Network.receivedResponseBody)", params2))
60
70
  }
61
71
 
@@ -227,15 +227,20 @@ data class ExpoReceivedResponseBodyParams(
227
227
  var body: String,
228
228
  var base64Encoded: Boolean
229
229
  ) : JsonSerializable {
230
- constructor(now: BigDecimal, requestId: RequestId, request: okhttp3.Request, response: okhttp3.Response) : this(
230
+ constructor(
231
+ now: BigDecimal,
232
+ requestId: RequestId,
233
+ request: okhttp3.Request,
234
+ response: okhttp3.Response,
235
+ body: okhttp3.ResponseBody
236
+ ) : this(
231
237
  requestId = requestId,
232
238
  body = "",
233
239
  base64Encoded = false
234
240
  ) {
235
- val rawBody = response.peekBody(ExpoNetworkInspectOkHttpNetworkInterceptor.MAX_BODY_SIZE)
236
- val contentType = rawBody.contentType()
241
+ val contentType = body.contentType()
237
242
  val isText = contentType?.type == "text" || (contentType?.type == "application" && contentType.subtype == "json")
238
- val bodyString = if (isText) rawBody.string() else rawBody.source().readByteString().base64()
243
+ val bodyString = if (isText) body.string() else body.source().readByteString().base64()
239
244
 
240
245
  this.body = bodyString
241
246
  this.base64Encoded = !isText
@@ -296,7 +296,7 @@ EX_REGISTER_MODULE();
296
296
  UIView<RCTComponentViewProtocol> *componentView = [uiManager viewForReactTag:(NSNumber *)viewId];
297
297
  UIView *view = [(ExpoFabricViewObjC *)componentView contentView];
298
298
  #else
299
- UIView *view = viewRegistry[viewId];
299
+ UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
300
300
  #endif
301
301
  block(view);
302
302
  }];
@@ -313,7 +313,7 @@ EX_REGISTER_MODULE();
313
313
  UIView<RCTComponentViewProtocol> *componentView = [uiManager viewForReactTag:(NSNumber *)viewId];
314
314
  UIView *view = [(ExpoFabricViewObjC *)componentView contentView];
315
315
  #else
316
- UIView *view = viewRegistry[viewId];
316
+ UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
317
317
  #endif
318
318
  block(view);
319
319
  }];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "1.12.3",
3
+ "version": "1.12.5",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -44,5 +44,5 @@
44
44
  "@testing-library/react-hooks": "^7.0.1",
45
45
  "expo-module-scripts": "^3.0.0"
46
46
  },
47
- "gitHead": "20f1765146f63f8fdc2543e8aba8b0e5140d1e05"
47
+ "gitHead": "470464ab5c25ae8bd45eb2a94d221ce51a8b2560"
48
48
  }