1616
1717package com.duckduckgo.duckchat.impl.helper
1818
19+ import com.duckduckgo.common.utils.DispatcherProvider
1920import com.duckduckgo.di.scopes.AppScope
2021import com.duckduckgo.duckchat.impl.ChatState
2122import com.duckduckgo.duckchat.impl.ChatState.HIDE
@@ -28,6 +29,7 @@ import com.duckduckgo.duckchat.impl.store.DuckChatDataStore
2829import com.duckduckgo.js.messaging.api.JsCallbackData
2930import com.squareup.anvil.annotations.ContributesBinding
3031import kotlinx.coroutines.runBlocking
32+ import kotlinx.coroutines.withContext
3133import org.json.JSONObject
3234import java.util.regex.Pattern
3335import javax.inject.Inject
@@ -47,7 +49,9 @@ class RealDuckChatJSHelper @Inject constructor(
4749 private val duckChatPixels : DuckChatPixels ,
4850 private val dataStore : DuckChatDataStore ,
4951 private val duckAiMetricCollector : DuckAiMetricCollector ,
52+ private val dispatchers : DispatcherProvider ,
5053) : DuckChatJSHelper {
54+ private val migrationItems = mutableListOf<String >()
5155 override suspend fun processJsCallbackMessage (
5256 featureName : String ,
5357 method : String ,
@@ -117,6 +121,22 @@ class RealDuckChatJSHelper @Inject constructor(
117121 null
118122 }
119123
124+ METHOD_STORE_MIGRATION_DATA -> id?.let {
125+ getStoreMigrationDataResponse(featureName, method, it, data)
126+ }
127+
128+ METHOD_GET_MIGRATION_INFO -> id?.let {
129+ getMigrationInfoResponse(featureName, method, it)
130+ }
131+
132+ METHOD_GET_MIGRATION_DATA_BY_INDEX -> id?.let {
133+ getMigrationDataByIndexResponse(featureName, method, it, data)
134+ }
135+
136+ METHOD_CLEAR_MIGRATION_DATA -> id?.let {
137+ getClearMigrationDataResponse(featureName, method, it)
138+ }
139+
120140 else -> null
121141 }
122142
@@ -148,6 +168,7 @@ class RealDuckChatJSHelper @Inject constructor(
148168 put(SUPPORTS_NATIVE_CHAT_INPUT , false )
149169 put(SUPPORTS_CHAT_ID_RESTORATION , duckChat.isDuckChatFullScreenModeEnabled())
150170 put(SUPPORTS_IMAGE_UPLOAD , duckChat.isImageUploadEnabled())
171+ put(SUPPORTS_STANDALONE_MIGRATION , duckChat.isStandaloneMigrationEnabled())
151172 }
152173 return JsCallbackData (jsonPayload, featureName, method, id)
153174 }
@@ -191,6 +212,90 @@ class RealDuckChatJSHelper @Inject constructor(
191212 }
192213 }
193214
215+ /* *
216+ * Accept incoming JSON payload { "serializedMigrationFile": "..." }
217+ * Store the string value in an ordered list for later retrieval
218+ */
219+ private suspend fun getStoreMigrationDataResponse (
220+ featureName : String ,
221+ method : String ,
222+ id : String ,
223+ data : JSONObject ? ,
224+ ): JsCallbackData {
225+ return withContext(dispatchers.io()) {
226+ val item = data?.optString(SERIALIZED_MIGRATION_FILE )
227+ val jsonPayload = JSONObject ()
228+ if (item != null && item != JSONObject .NULL ) {
229+ migrationItems.add(item)
230+ jsonPayload.put(OK , true )
231+ } else {
232+ jsonPayload.put(OK , false )
233+ jsonPayload.put(REASON , " Missing or invalid serializedMigrationFile" )
234+ }
235+ JsCallbackData (jsonPayload, featureName, method, id)
236+ }
237+ }
238+
239+ /* *
240+ * Return the count of strings previously stored.
241+ * It's ok to return 0 if no items have been stored
242+ */
243+ private suspend fun getMigrationInfoResponse (
244+ featureName : String ,
245+ method : String ,
246+ id : String ,
247+ ): JsCallbackData {
248+ return withContext(dispatchers.io()) {
249+ val count = migrationItems.size
250+ val jsonPayload = JSONObject ().apply {
251+ put(OK , true )
252+ put(COUNT , count)
253+ }
254+ JsCallbackData (jsonPayload, featureName, method, id)
255+ }
256+ }
257+
258+ /* *
259+ * Try to lookup a string by index
260+ * - when found, return { ok: true, serializedMigrationFile: '...' }
261+ * - when missing, return { ok: false, reason: '...' }
262+ */
263+ private suspend fun getMigrationDataByIndexResponse (
264+ featureName : String ,
265+ method : String ,
266+ id : String ,
267+ data : JSONObject ? ,
268+ ): JsCallbackData {
269+ return withContext(dispatchers.io()) {
270+ val index = data?.optInt(INDEX , - 1 ) ? : - 1
271+ val value = migrationItems.getOrNull(index)
272+ val jsonPayload = JSONObject ()
273+ if (value == null ) {
274+ jsonPayload.put(OK , false )
275+ jsonPayload.put(REASON , " nothing at index: $index " )
276+ } else {
277+ jsonPayload.put(OK , true )
278+ jsonPayload.put(SERIALIZED_MIGRATION_FILE , value)
279+ }
280+ JsCallbackData (jsonPayload, featureName, method, id)
281+ }
282+ }
283+
284+ /* *
285+ * Clear migration data, returning { ok: true } when complete
286+ */
287+ private suspend fun getClearMigrationDataResponse (
288+ featureName : String ,
289+ method : String ,
290+ id : String ,
291+ ): JsCallbackData {
292+ return withContext(dispatchers.io()) {
293+ migrationItems.clear()
294+ val jsonPayload = JSONObject ().apply { put(OK , true ) }
295+ JsCallbackData (jsonPayload, featureName, method, id)
296+ }
297+ }
298+
194299 companion object {
195300 const val DUCK_CHAT_FEATURE_NAME = " aiChat"
196301 private const val METHOD_GET_AI_CHAT_NATIVE_HANDOFF_DATA = " getAIChatNativeHandoffData"
@@ -210,12 +315,25 @@ class RealDuckChatJSHelper @Inject constructor(
210315 private const val SUPPORTS_NATIVE_CHAT_INPUT = " supportsNativeChatInput"
211316 private const val SUPPORTS_IMAGE_UPLOAD = " supportsImageUpload"
212317 private const val SUPPORTS_CHAT_ID_RESTORATION = " supportsURLChatIDRestoration"
318+ private const val SUPPORTS_STANDALONE_MIGRATION = " supportsStandaloneMigration"
213319 private const val REPORT_METRIC = " reportMetric"
214320 private const val PLATFORM = " platform"
215321 private const val ANDROID = " android"
216322 const val SELECTOR = " selector"
217323 private const val DEFAULT_SELECTOR = " 'user-prompt'"
218324 private const val SUCCESS = " success"
219325 private const val ERROR = " error"
326+ private const val OK = " ok"
327+ private const val REASON = " reason"
328+
329+ // Migration messaging constants
330+ private const val METHOD_STORE_MIGRATION_DATA = " storeMigrationData"
331+ private const val METHOD_GET_MIGRATION_INFO = " getMigrationInfo"
332+ private const val METHOD_GET_MIGRATION_DATA_BY_INDEX = " getMigrationDataByIndex"
333+ private const val METHOD_CLEAR_MIGRATION_DATA = " clearMigrationData"
334+
335+ private const val SERIALIZED_MIGRATION_FILE = " serializedMigrationFile"
336+ private const val COUNT = " count"
337+ private const val INDEX = " index"
220338 }
221339}
0 commit comments