<lambda>null1 package expo.modules.updates.manifest
2 
3 import android.util.Log
4 import expo.modules.jsonutils.require
5 import expo.modules.structuredheaders.Dictionary
6 import expo.modules.structuredheaders.StringItem
7 import expo.modules.updates.UpdatesConfiguration
8 import expo.modules.updates.db.UpdatesDatabase
9 import org.json.JSONObject
10 
11 /**
12  * Utility methods for reading and writing JSON metadata from manifests (e.g. `serverDefinedHeaders`
13  * and `manifestFilters`, both used for rollouts) to and from SQLite.
14  */
15 object ManifestMetadata {
16   private val TAG = ManifestMetadata::class.java.simpleName
17 
18   private const val EXTRA_PARAMS_KEY = "extraParams"
19   private const val MANIFEST_SERVER_DEFINED_HEADERS_KEY = "serverDefinedHeaders"
20   private const val MANIFEST_FILTERS_KEY = "manifestFilters"
21 
22   private fun getJSONObject(
23     key: String,
24     database: UpdatesDatabase,
25     configuration: UpdatesConfiguration
26   ): JSONObject? {
27     return try {
28       val jsonString = database.jsonDataDao()!!
29         .loadJSONStringForKey(key, configuration.scopeKey!!)
30       if (jsonString != null) JSONObject(jsonString) else null
31     } catch (e: Exception) {
32       Log.e(TAG, "Error retrieving $key from database", e)
33       null
34     }
35   }
36 
37   @JvmStatic fun getServerDefinedHeaders(
38     database: UpdatesDatabase,
39     configuration: UpdatesConfiguration
40   ): JSONObject? {
41     return getJSONObject(MANIFEST_SERVER_DEFINED_HEADERS_KEY, database, configuration)
42   }
43 
44   fun getManifestFilters(
45     database: UpdatesDatabase,
46     configuration: UpdatesConfiguration
47   ): JSONObject? {
48     return getJSONObject(MANIFEST_FILTERS_KEY, database, configuration)
49   }
50 
51   fun getExtraParams(
52     database: UpdatesDatabase,
53     configuration: UpdatesConfiguration
54   ): Map<String, String>? {
55     return getJSONObject(EXTRA_PARAMS_KEY, database, configuration)?.asStringStringMap()
56   }
57 
58   fun setExtraParam(
59     database: UpdatesDatabase,
60     configuration: UpdatesConfiguration,
61     key: String,
62     value: String?
63   ) {
64     // this is done within a transaction to ensure consistency
65     database.jsonDataDao()!!.updateJSONStringForKey(EXTRA_PARAMS_KEY, configuration.scopeKey!!) { previousValue ->
66       val jsonObject = previousValue?.let { JSONObject(it) }
67       val extraParamsToWrite = (jsonObject?.asStringStringMap()?.toMutableMap() ?: mutableMapOf()).also {
68         if (value != null) {
69           it[key] = value
70         } else {
71           it.remove(key)
72         }
73       }.toMap()
74 
75       // ensure that this can be serialized to a structured-header dictionary
76       // this will throw for invalid values
77       Dictionary.valueOf(extraParamsToWrite.mapValues { elem -> StringItem.valueOf(elem.value) }).serialize()
78 
79       JSONObject(extraParamsToWrite).toString()
80     }
81   }
82 
83   fun saveMetadata(
84     responseHeaderData: ResponseHeaderData,
85     database: UpdatesDatabase,
86     configuration: UpdatesConfiguration
87   ) {
88     val fieldsToSet = mutableMapOf<String, String>()
89     if (responseHeaderData.serverDefinedHeaders != null) {
90       fieldsToSet[MANIFEST_SERVER_DEFINED_HEADERS_KEY] = responseHeaderData.serverDefinedHeaders.toString()
91     }
92     if (responseHeaderData.manifestFilters != null) {
93       fieldsToSet[MANIFEST_FILTERS_KEY] = responseHeaderData.manifestFilters.toString()
94     }
95     if (fieldsToSet.isNotEmpty()) {
96       database.jsonDataDao()!!.setMultipleFields(fieldsToSet, configuration.scopeKey!!)
97     }
98   }
99 
100   private fun JSONObject.asStringStringMap(): Map<String, String> {
101     return buildMap {
102       this@asStringStringMap.keys().asSequence().forEach { key ->
103         this[key] = this@asStringStringMap.require(key)
104       }
105     }
106   }
107 }
108