expo-modules-core 55.0.3 → 55.0.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,16 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 55.0.5 — 2026-01-27
14
+
15
+ _This version does not introduce any user-facing changes._
16
+
17
+ ## 55.0.4 — 2026-01-26
18
+
19
+ ### 🐛 Bug fixes
20
+
21
+ - [Android] Added missing checks in the promise implementation. ([#42467](https://github.com/expo/expo/pull/42467) by [@lukmccall](https://github.com/lukmccall))
22
+
13
23
  ## 55.0.3 — 2026-01-22
14
24
 
15
25
  ### 🐛 Bug fixes
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
29
29
  }
30
30
 
31
31
  group = 'host.exp.exponent'
32
- version = '55.0.3'
32
+ version = '55.0.5'
33
33
 
34
34
  def isExpoModulesCoreTests = {
35
35
  Gradle gradle = getGradle()
@@ -96,7 +96,7 @@ android {
96
96
  defaultConfig {
97
97
  consumerProguardFiles 'proguard-rules.pro'
98
98
  versionCode 1
99
- versionName "55.0.3"
99
+ versionName "55.0.5"
100
100
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
101
101
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
102
102
 
@@ -13,13 +13,13 @@
13
13
  #include <fbjni/fbjni.h>
14
14
 
15
15
  #include <memory>
16
+ #include <shared_mutex>
16
17
 
17
18
  namespace jni = facebook::jni;
18
19
  namespace jsi = facebook::jsi;
19
20
 
20
21
  namespace expo {
21
22
 
22
-
23
23
  void JSIContext::registerNatives() {
24
24
  registerHybrid({
25
25
  makeNativeMethod("evaluateScript", JSIContext::evaluateScript),
@@ -263,14 +263,26 @@ bool JSIContext::wasDeallocated() const noexcept {
263
263
  return wasDeallocated_;
264
264
  }
265
265
 
266
- std::unordered_map<uintptr_t, JSIContext *> jsiContexts;
266
+ static std::unordered_map<uintptr_t, JSIContext *> jsiContexts;
267
+ static std::shared_mutex jsiContextsMutex;
267
268
 
268
269
  void bindJSIContext(const jsi::Runtime &runtime, JSIContext *jsiContext) {
270
+ std::unique_lock lock(jsiContextsMutex);
269
271
  jsiContexts[reinterpret_cast<uintptr_t>(&runtime)] = jsiContext;
270
272
  }
271
273
 
272
274
  void unbindJSIContext(const jsi::Runtime &runtime) {
275
+ std::unique_lock lock(jsiContextsMutex);
273
276
  jsiContexts.erase(reinterpret_cast<uintptr_t>(&runtime));
274
277
  }
275
278
 
279
+ JSIContext *getJSIContext(const jsi::Runtime &runtime) {
280
+ std::shared_lock lock(jsiContextsMutex);
281
+ const auto iterator = jsiContexts.find(reinterpret_cast<uintptr_t>(&runtime));
282
+ if (iterator == jsiContexts.end()) {
283
+ throw std::invalid_argument("JSIContext for the given runtime doesn't exist");
284
+ }
285
+ return iterator->second;
286
+ }
287
+
276
288
  } // namespace expo
@@ -167,14 +167,9 @@ private:
167
167
  ) noexcept;
168
168
  };
169
169
 
170
- /**
171
- * We are binding the JSIContext to the runtime using a thread-local map.
172
- * This is a simplification of how we're accessing the JSIContext from different places.
173
- */
174
- extern std::unordered_map<uintptr_t, JSIContext *> jsiContexts;
175
-
176
170
  /**
177
171
  * Binds the JSIContext to the runtime.
172
+ * Thread-safe: uses exclusive lock.
178
173
  * @param runtime
179
174
  * @param jsiContext
180
175
  */
@@ -182,22 +177,18 @@ void bindJSIContext(const jsi::Runtime &runtime, JSIContext *jsiContext);
182
177
 
183
178
  /**
184
179
  * Unbinds the JSIContext from the runtime.
180
+ * Thread-safe: uses exclusive lock.
185
181
  * @param runtime
186
182
  */
187
183
  void unbindJSIContext(const jsi::Runtime &runtime);
188
184
 
189
185
  /**
190
186
  * Gets the JSIContext for the given runtime.
187
+ * Thread-safe: uses exclusive lock.
191
188
  * @param runtime
192
189
  * @return JSIContext * - it should never be stored when received from this function.
193
- * It might throw an exception if the JSIContext for the given runtime doesn't exist.
190
+ * @throws std::invalid_argument if the JSIContext for the given runtime doesn't exist.
194
191
  */
195
- inline JSIContext *getJSIContext(const jsi::Runtime &runtime) {
196
- const auto iterator = jsiContexts.find(reinterpret_cast<uintptr_t>(&runtime));
197
- if (iterator == jsiContexts.end()) {
198
- throw std::invalid_argument("JSIContext for the given runtime doesn't exist");
199
- }
200
- return iterator->second;
201
- }
192
+ JSIContext *getJSIContext(const jsi::Runtime &runtime);
202
193
 
203
194
  } // namespace expo
@@ -27,7 +27,7 @@ std::shared_ptr<jsi::Function> JavaScriptFunction::get() {
27
27
 
28
28
  jobject JavaScriptFunction::invoke(
29
29
  jni::alias_ref<JavaScriptObject::javaobject> jsThis,
30
- jni::alias_ref<jni::JArrayClass<jni::JObject>> args,
30
+ jni::alias_ref<jni::JArrayClass<jobject>> args,
31
31
  jni::alias_ref<ExpectedType::javaobject> expectedReturnType
32
32
  ) {
33
33
  auto runtime = runtimeHolder.lock();
@@ -35,28 +35,20 @@ jobject JavaScriptFunction::invoke(
35
35
  auto &rawRuntime = runtime->get();
36
36
 
37
37
  JNIEnv *env = jni::Environment::current();
38
-
39
- size_t size = args->size();
40
- std::vector<jsi::Value> convertedArgs;
41
- convertedArgs.reserve(size);
42
-
43
- for (size_t i = 0; i < size; i++) {
44
- jni::local_ref<jobject> arg = args->getElement(i);
45
- convertedArgs.push_back(convert(env, rawRuntime, arg));
46
- }
38
+ std::vector<jsi::Value> convertedArgs = convertArray(env, rawRuntime, args);
47
39
 
48
40
  // TODO(@lukmccall): add better error handling
49
41
  jsi::Value result = jsThis == nullptr ?
50
42
  jsFunction->call(
51
43
  rawRuntime,
52
44
  (const jsi::Value *) convertedArgs.data(),
53
- size
45
+ convertedArgs.size()
54
46
  ) :
55
47
  jsFunction->callWithThis(
56
48
  rawRuntime,
57
49
  *(jsThis->cthis()->get()),
58
50
  (const jsi::Value *) convertedArgs.data(),
59
- size
51
+ convertedArgs.size()
60
52
  );
61
53
 
62
54
  auto converter = AnyType(jni::make_local(expectedReturnType)).converter;
@@ -49,7 +49,7 @@ private:
49
49
 
50
50
  jobject invoke(
51
51
  jni::alias_ref<jni::HybridClass<JavaScriptObject, Destructible>::javaobject> jsThis,
52
- jni::alias_ref<jni::JArrayClass<jni::JObject>> args,
52
+ jni::alias_ref<jni::JArrayClass<jobject>> args,
53
53
  jni::alias_ref<ExpectedType::javaobject> expectedReturnType
54
54
  );
55
55
  };
@@ -14,11 +14,7 @@ namespace expo {
14
14
  JavaScriptRuntime::JavaScriptRuntime(
15
15
  jsi::Runtime *runtime,
16
16
  std::shared_ptr<react::CallInvoker> jsInvoker
17
- ) : jsInvoker(std::move(jsInvoker)) {
18
- // Creating a shared pointer that points to the runtime but doesn't own it, thus doesn't release it.
19
- // In this code flow, the runtime should be owned by something else like the CatalystInstance.
20
- // See explanation for constructor (8): https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
21
- this->runtime = std::shared_ptr<jsi::Runtime>(std::shared_ptr<jsi::Runtime>(), runtime);
17
+ ) : jsInvoker(std::move(jsInvoker)), runtime(runtime) {
22
18
  }
23
19
 
24
20
  jsi::Runtime &JavaScriptRuntime::get() const noexcept {
@@ -76,6 +76,12 @@ public:
76
76
  std::shared_ptr<react::CallInvoker> jsInvoker;
77
77
 
78
78
  private:
79
- std::shared_ptr<jsi::Runtime> runtime;
79
+ /**
80
+ * Raw pointer to the runtime. We do not own this - it's managed by React Native.
81
+ * The runtime's lifetime is guaranteed to exceed JavaScriptRuntime's lifetime,
82
+ * as JSIContext::prepareForDeallocation() invalidates all weak references before
83
+ * the runtime is deallocated.
84
+ */
85
+ jsi::Runtime *runtime;
80
86
  };
81
87
  } // namespace expo
@@ -32,6 +32,24 @@ jsi::Value convert(
32
32
  const jni::local_ref<jobject> &value
33
33
  );
34
34
 
35
+ template<typename RefType>
36
+ std::vector<jsi::Value> convertArray(
37
+ JNIEnv *env,
38
+ jsi::Runtime &rt,
39
+ RefType &values
40
+ ) {
41
+ size_t size = values->size();
42
+ std::vector<jsi::Value> convertedValues;
43
+ convertedValues.reserve(size);
44
+
45
+ for (size_t i = 0; i < size; i++) {
46
+ jni::local_ref<jobject> value = values->getElement(i);
47
+ convertedValues.push_back(convert(env, rt, value));
48
+ }
49
+
50
+ return convertedValues;
51
+ }
52
+
35
53
  /**
36
54
  * Convert a string with FollyDynamicExtensionConverter support.
37
55
  */
@@ -2,12 +2,16 @@
2
2
 
3
3
  #if WORKLETS_ENABLED
4
4
 
5
+ #include "../types/JNIToJSIConverter.h"
6
+
5
7
  namespace expo {
6
8
 
7
9
  void Worklet::registerNatives() {
8
10
  javaClassLocal()->registerNatives({
9
11
  makeNativeMethod("schedule", Worklet::schedule),
12
+ makeNativeMethod("schedule", Worklet::scheduleWithArgs),
10
13
  makeNativeMethod("execute", Worklet::execute),
14
+ makeNativeMethod("execute", Worklet::executeWithArgs),
11
15
  });
12
16
  }
13
17
 
@@ -17,7 +21,10 @@ void Worklet::schedule(
17
21
  jni::alias_ref<Serializable::javaobject> synchronizable
18
22
  ) {
19
23
  auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
20
- auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(synchronizable->cthis()->getSerializable());
24
+ auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
25
+ synchronizable->cthis()->getSerializable()
26
+ );
27
+
21
28
  workletRuntime->schedule(std::move(worklet));
22
29
  }
23
30
 
@@ -27,11 +34,61 @@ void Worklet::execute(
27
34
  jni::alias_ref<Serializable::javaobject> synchronizable
28
35
  ) {
29
36
  auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
30
- auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(synchronizable->cthis()->getSerializable());
37
+ auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
38
+ synchronizable->cthis()->getSerializable()
39
+ );
31
40
 
32
41
  workletRuntime->runSync(worklet);
33
42
  }
34
43
 
44
+ void Worklet::scheduleWithArgs(
45
+ jni::alias_ref<Worklet::javaobject> self,
46
+ jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
47
+ jni::alias_ref<Serializable::javaobject> synchronizable,
48
+ jni::alias_ref<jni::JArrayClass<jobject>> args
49
+ ) {
50
+ auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
51
+ auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
52
+ synchronizable->cthis()->getSerializable()
53
+ );
54
+
55
+ workletRuntime->schedule([&worklet, globalArgs = jni::make_global(args)](jsi::Runtime &rt) mutable {
56
+ JNIEnv *env = jni::Environment::current();
57
+ std::vector<jsi::Value> convertedArgs = convertArray(env, rt, globalArgs);
58
+
59
+ auto func = worklet->toJSValue(rt).asObject(rt).asFunction(rt);
60
+ func.call(
61
+ rt,
62
+ (const jsi::Value *) convertedArgs.data(),
63
+ convertedArgs.size()
64
+ );
65
+ });
66
+ }
67
+
68
+ void Worklet::executeWithArgs(
69
+ jni::alias_ref<Worklet::javaobject> self,
70
+ jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
71
+ jni::alias_ref<Serializable::javaobject> synchronizable,
72
+ jni::alias_ref<jni::JArrayClass<jobject>> args
73
+ ) {
74
+ auto workletRuntime = workletRuntimeHolder->cthis()->workletRuntime.lock();
75
+ auto worklet = std::dynamic_pointer_cast<worklets::SerializableWorklet>(
76
+ synchronizable->cthis()->getSerializable()
77
+ );
78
+
79
+ workletRuntime->runSync([&args, &worklet](jsi::Runtime &rt) {
80
+ JNIEnv *env = jni::Environment::current();
81
+ std::vector<jsi::Value> convertedArgs = convertArray(env, rt, args);
82
+
83
+ auto func = worklet->toJSValue(rt).asObject(rt).asFunction(rt);
84
+ func.call(
85
+ rt,
86
+ (const jsi::Value *) convertedArgs.data(),
87
+ convertedArgs.size()
88
+ );
89
+ });
90
+ }
91
+
35
92
  } // namespace expo
36
93
 
37
94
  #endif
@@ -33,6 +33,20 @@ public:
33
33
  jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
34
34
  jni::alias_ref<Serializable::javaobject> synchronizable
35
35
  );
36
+
37
+ static void scheduleWithArgs(
38
+ jni::alias_ref<Worklet::javaobject> self,
39
+ jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
40
+ jni::alias_ref<Serializable::javaobject> synchronizable,
41
+ jni::alias_ref<jni::JArrayClass<jobject>> args
42
+ );
43
+
44
+ static void executeWithArgs(
45
+ jni::alias_ref<Worklet::javaobject> self,
46
+ jni::alias_ref<WorkletNativeRuntime::javaobject> workletRuntimeHolder,
47
+ jni::alias_ref<Serializable::javaobject> synchronizable,
48
+ jni::alias_ref<jni::JArrayClass<jobject>> args
49
+ );
36
50
  };
37
51
 
38
52
  } // namespace expo
@@ -264,16 +264,25 @@ class AppContext(
264
264
  }
265
265
 
266
266
  internal fun onDestroy() = trace("AppContext.onDestroy") {
267
- hostingRuntimeContext.reactContext?.removeLifecycleEventListener(reactLifecycleDelegate)
268
- registry.post(EventName.MODULE_DESTROY)
269
- registry.cleanUp()
267
+ runtime.reactContext?.run {
268
+ removeLifecycleEventListener(reactLifecycleDelegate)
269
+ removeActivityEventListener(reactLifecycleDelegate)
270
+ }
271
+
272
+ with(registry) {
273
+ post(EventName.MODULE_DESTROY)
274
+ cleanUp()
275
+ }
276
+
270
277
  modulesQueue.cancel(ContextDestroyedException())
271
278
  mainQueue.cancel(ContextDestroyedException())
272
279
  backgroundCoroutineScope.cancel(ContextDestroyedException())
273
- hostingRuntimeContext.deallocate()
280
+
281
+ runtime.deallocate()
274
282
  if (uiRuntimeHolder.isInitialized()) {
275
283
  uiRuntime.deallocate()
276
284
  }
285
+
277
286
  logger.info("✅ AppContext was destroyed")
278
287
  }
279
288
 
@@ -45,11 +45,11 @@ class PromiseImpl @DoNotStrip internal constructor(
45
45
  callback.invoke(result)
46
46
  }
47
47
 
48
- override fun resolve(result: Collection<Any?>) {
48
+ override fun resolve(result: Collection<Any?>) = checkIfWasSettled {
49
49
  callback.invoke(result)
50
50
  }
51
51
 
52
- override fun resolve(result: Map<String, Any?>) {
52
+ override fun resolve(result: Map<String, Any?>) = checkIfWasSettled {
53
53
  callback.invoke(result)
54
54
  }
55
55
 
@@ -62,11 +62,11 @@ class PromiseImpl @DoNotStrip internal constructor(
62
62
  private inline fun checkIfWasSettled(body: () -> Unit) {
63
63
  if (wasSettled) {
64
64
  val exception = PromiseAlreadySettledException(fullFunctionName ?: "unknown")
65
- val errorManager = appContextHolder?.get()?.errorManager
65
+ val jsLogger = appContextHolder?.get()?.jsLogger
66
66
  // We want to report that a promise was settled twice in the development build.
67
67
  // However, in production, the app should crash.
68
- if (BuildConfig.DEBUG && errorManager != null) {
69
- errorManager.reportExceptionToLogBox(exception)
68
+ if (BuildConfig.DEBUG && jsLogger != null) {
69
+ jsLogger.error("Trying to resolve promise that was already settled", exception)
70
70
  logger.error("Trying to resolve promise that was already settled", exception)
71
71
  return
72
72
  }
@@ -1,6 +1,7 @@
1
1
  package expo.modules.kotlin.jni.worklets
2
2
 
3
3
  import expo.modules.kotlin.runtime.WorkletRuntime
4
+ import expo.modules.kotlin.types.JSTypeConverter
4
5
 
5
6
  class Worklet internal constructor(
6
7
  private val serializable: Serializable
@@ -19,13 +20,45 @@ class Worklet internal constructor(
19
20
  execute(runtimeHolder, serializable)
20
21
  }
21
22
 
23
+ fun schedule(runtime: WorkletRuntime, vararg arguments: Any?) {
24
+ val runtimeHolder = runtime.enforceHolder
25
+
26
+ val convertedArgs = arguments.map {
27
+ JSTypeConverter.convertToJSValue(it, useExperimentalConverter = true)
28
+ }.toTypedArray()
29
+
30
+ schedule(runtimeHolder, serializable, convertedArgs)
31
+ }
32
+
33
+ fun execute(runtime: WorkletRuntime, vararg arguments: Any?) {
34
+ val runtimeHolder = runtime.enforceHolder
35
+
36
+ val convertedArgs = arguments.map {
37
+ JSTypeConverter.convertToJSValue(it, useExperimentalConverter = true)
38
+ }.toTypedArray()
39
+
40
+ execute(runtimeHolder, serializable, convertedArgs)
41
+ }
42
+
22
43
  private external fun schedule(
23
44
  workletNativeRuntime: WorkletNativeRuntime,
24
45
  serializable: Serializable
25
46
  )
26
47
 
48
+ private external fun schedule(
49
+ workletNativeRuntime: WorkletNativeRuntime,
50
+ serializable: Serializable,
51
+ args: Array<Any?>
52
+ )
53
+
27
54
  private external fun execute(
28
55
  workletNativeRuntime: WorkletNativeRuntime,
29
56
  serializable: Serializable
30
57
  )
58
+
59
+ private external fun execute(
60
+ workletNativeRuntime: WorkletNativeRuntime,
61
+ serializable: Serializable,
62
+ args: Array<Any?>
63
+ )
31
64
  }
@@ -10,6 +10,11 @@ protocol AnyConstantDefinition {
10
10
  Creates the JavaScript object representing the constant property descriptor.
11
11
  */
12
12
  func buildDescriptor(appContext: AppContext) throws -> JavaScriptObject
13
+
14
+ /**
15
+ Returns the raw value of the constant for encoding purposes.
16
+ */
17
+ func getRawValue() -> Any?
13
18
  }
14
19
 
15
20
  public final class ConstantDefinition<ReturnType>: AnyDefinition, AnyConstantDefinition {
@@ -59,6 +64,10 @@ public final class ConstantDefinition<ReturnType>: AnyDefinition, AnyConstantDef
59
64
  return try getter?()
60
65
  }
61
66
 
67
+ internal func getRawValue() -> Any? {
68
+ return try? getter?()
69
+ }
70
+
62
71
  /**
63
72
  Creates the JavaScript function that will be used as a getter of the constant.
64
73
  */
@@ -16,7 +16,12 @@ class ModuleDefinitionEncoder: Encodable {
16
16
  func encode(to encoder: Encoder) throws {
17
17
  var container = encoder.container(keyedBy: CodingKeys.self)
18
18
  try container.encode(definition.name, forKey: .name)
19
- try container.encode(definition.legacyConstants.map({ LegacyConstantsDefinitionEncoder($0) }), forKey: .constants)
19
+ var allConstants: [[ConstantEncoder]] = []
20
+ allConstants.append(contentsOf: definition.legacyConstants.map({ LegacyConstantsDefinitionEncoder($0).getEncoders() }))
21
+ if !definition.constants.isEmpty {
22
+ allConstants.append(definition.constants.values.map({ ConstantEncoder($0.name, value: $0.getRawValue()) }))
23
+ }
24
+ try container.encode(allConstants, forKey: .constants)
20
25
  try container.encode(definition.properties.values.map({ PropertyDefinitionEncoder($0) }), forKey: .properties)
21
26
  try container.encode(definition.functions.values.map({ FunctionDefinitionEncoder($0) }), forKey: .functions)
22
27
  try container.encode(definition.views.values.map({ ViewDefinitionEncoder($0) }), forKey: .views)
@@ -130,10 +135,10 @@ class ConstantEncoder: Encodable {
130
135
  case nil:
131
136
  try container.encodeNil(forKey: .value)
132
137
  try container.encode("null", forKey: .type)
133
- case let value as [String: Any]:
138
+ case _ as [String: Any]:
134
139
  try container.encodeNil(forKey: .value)
135
140
  try container.encode("object", forKey: .type)
136
- case let value as [Any]:
141
+ case _ as [Any]:
137
142
  try container.encodeNil(forKey: .value)
138
143
  try container.encode("array", forKey: .type)
139
144
  default:
@@ -143,24 +148,16 @@ class ConstantEncoder: Encodable {
143
148
  }
144
149
  }
145
150
 
146
- class LegacyConstantsDefinitionEncoder: Encodable {
151
+ class LegacyConstantsDefinitionEncoder {
147
152
  private let definition: ConstantsDefinition
148
153
 
149
154
  init(_ definition: ConstantsDefinition) {
150
155
  self.definition = definition
151
156
  }
152
157
 
153
- enum CodingKeys: String, CodingKey {
154
- case name
155
- case value
156
- }
157
-
158
- func encode(to encoder: Encoder) throws {
159
- var container = encoder.unkeyedContainer()
158
+ func getEncoders() -> [ConstantEncoder] {
160
159
  let constants = definition.body()
161
- for (key, value) in constants {
162
- try container.encode(ConstantEncoder(key, value: value))
163
- }
160
+ return constants.map { (key, value) in ConstantEncoder(key, value: value) }
164
161
  }
165
162
  }
166
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "55.0.3",
3
+ "version": "55.0.5",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@testing-library/react-native": "^13.2.0",
67
- "expo-module-scripts": "^55.0.1"
67
+ "expo-module-scripts": "^55.0.2"
68
68
  },
69
- "gitHead": "4728ba25fbd4d5835780306de78a83bd1628e271"
69
+ "gitHead": "220594d473a3100248087151004ae4acb7282d5f"
70
70
  }